<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <meta name="keywords" content="Hexo Theme Keep">
    <meta name="description" content="Hexo Theme Keep">
    <meta name="author" content="Blank">
    
    <title>
        
            RocketMQ-03 |
        
        Blankの博客
    </title>
    
<link rel="stylesheet" href="/css/style.css">

    <link rel="shortcut icon" href="/images/logo.jpg">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/font/css/fontawesome.min.css">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/font/css/regular.min.css">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/font/css/solid.min.css">
    <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/font/css/brands.min.css">
    <script id="hexo-configurations">
    let KEEP = window.KEEP || {}
    KEEP.hexo_config = {"hostname":"example.com","root":"/","language":"zh-CN","path":"search.json"}
    KEEP.theme_config = {"toc":{"enable":true,"number":true,"expand_all":true,"init_open":true},"style":{"primary_color":"#0066cc","logo":"/images/logo.jpg","favicon":"/images/logo.jpg","avatar":"/images/logo.jpg","font_size":"18px","font_family":"STKaiti","hover":{"shadow":true,"scale":true},"first_screen":{"enable":true,"header_transparent":true,"background_img":"/images/bg.svg","description":"Keep writing and Keep loving.","font_color":null,"hitokoto":true},"scroll":{"progress_bar":true,"percent":true}},"local_search":{"enable":true,"preload":true},"code_copy":{},"code_block":{"tools":{"enable":true,"style":"mac"},"highlight_theme":"default"},"side_tools":{},"pjax":{"enable":true},"lazyload":{"enable":true},"comment":{"enable":false,"use":"gitalk","valine":{"appid":null,"appkey":null,"server_urls":null,"placeholder":null},"gitalk":{"github_id":"5ober","github_admins":"5ober","repository":"hexo-blog-comments","client_id":"40d85a7b36388c0b5094","client_secret":"6c7eab92d24b6f1bdfbcdf077c73e86841b35d5e","proxy":null},"twikoo":{"env_id":null,"region":null,"version":"1.6.8"},"waline":{"server_url":null,"reaction":false,"version":2}},"post":{"author_label":{"enable":true,"auto":false,"custom_label_list":["Trainee","Engineer","Java工程师"]},"word_count":{"enable":true,"wordcount":true,"min2read":true},"img_align":"left","copyright_info":true},"version":"3.6.1"}
    KEEP.language_ago = {"second":"%s 秒前","minute":"%s 分钟前","hour":"%s 小时前","day":"%s 天前","week":"%s 周前","month":"%s 个月前","year":"%s 年前"}
    KEEP.language_code_block = {"copy":"复制代码","copied":"已复制","fold":"折叠代码块","folded":"已折叠"}
    KEEP.language_copy_copyright = {"copy":"复制版权信息","copied":"已复制","title":"原文标题","author":"原文作者","link":"原文链接"}
  </script>
<meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="Blankの博客" type="application/atom+xml">
</head>


<body>
<div class="progress-bar-container">
    
        <span class="scroll-progress-bar"></span>
    

    
        <span class="pjax-progress-bar"></span>
        <i class="pjax-progress-icon fas fa-circle-notch fa-spin"></i>
    
</div>


<main class="page-container">

    

    <div class="page-main-content">

        <div class="page-main-content-top">
            
<header class="header-wrapper">

    <div class="header-content">
        <div class="left">
            
                <a class="logo-image" href="/">
                    <img src="/images/logo.jpg">
                </a>
            
            <a class="logo-title" href="/">
               Blankの博客
            </a>
        </div>

        <div class="right">
            <div class="pc">
                <ul class="menu-list">
                    
                        <li class="menu-item">
                            <a class=""
                               href="/"
                            >
                                首页
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/archives"
                            >
                                归档
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/tags"
                            >
                                标签
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/categories"
                            >
                                分类
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/links"
                            >
                                友链
                            </a>
                        </li>
                    
                        <li class="menu-item">
                            <a class=""
                               href="/about"
                            >
                                关于
                            </a>
                        </li>
                    
                    
                        <li class="menu-item search search-popup-trigger">
                            <i class="fas fa-search"></i>
                        </li>
                    
                </ul>
            </div>
            <div class="mobile">
                
                    <div class="icon-item search search-popup-trigger"><i class="fas fa-search"></i></div>
                
                <div class="icon-item menu-bar">
                    <div class="menu-bar-middle"></div>
                </div>
            </div>
        </div>
    </div>

    <div class="header-drawer">
        <ul class="drawer-menu-list">
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/">首页</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/archives">归档</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/tags">标签</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/categories">分类</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/links">友链</a>
                </li>
            
                <li class="drawer-menu-item flex-center">
                    <a class=""
                       href="/about">关于</a>
                </li>
            
        </ul>
    </div>

    <div class="window-mask"></div>

</header>


        </div>

        <div class="page-main-content-middle">

            <div class="main-content">

                
                    <div class="fade-in-down-animation">
    <div class="post-page-container">
        <div class="article-content-container">

            <div class="article-title">
                <span class="title-hover-animation">RocketMQ-03</span>
            </div>

            
                <div class="article-header">
                    <div class="avatar">
                        <img src="/images/logo.jpg">
                    </div>
                    <div class="info">
                        <div class="author">
                            <span class="name">Blank</span>
                            
                                <span class="author-label">Java工程师</span>
                            
                        </div>
                        <div class="meta-info">
                            
<div class="article-meta-info">
    <span class="article-date article-meta-item">
        
            <i class="fa-regular fa-calendar-plus"></i>&nbsp;
        
        <span class="pc">2023-02-19 00:00:00</span>
        <span class="mobile">2023-02-19 00:00</span>
    </span>
    
        <span class="article-update-date article-meta-item">
        <i class="fas fa-file-pen"></i>&nbsp;
        <span class="pc">2023-03-22 09:57:15</span>
    </span>
    
    
        <span class="article-categories article-meta-item">
            <i class="fas fa-folder"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/categories/RocketMQ/">RocketMQ</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    
    
        <span class="article-tags article-meta-item">
            <i class="fas fa-tags"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/tags/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/">消息中间件</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    

    
    
        <span class="article-wordcount article-meta-item">
            <i class="fas fa-file-word"></i>&nbsp;<span>29.4k 字</span>
        </span>
    
    
        <span class="article-min2read article-meta-item">
            <i class="fas fa-clock"></i>&nbsp;<span>132 分钟</span>
        </span>
    
    
        <span class="article-pv article-meta-item">
            <i class="fas fa-eye"></i>&nbsp;<span id="busuanzi_value_page_pv"></span>
        </span>
    
</div>

                        </div>
                    </div>
                </div>
            

            <div class="article-content keep-markdown-body">
                

                <h1 id="RocketMQ-03"><a href="#RocketMQ-03" class="headerlink" title="RocketMQ-03"></a>RocketMQ-03</h1><ul>
<li>1.1 消息存储<ul>
<li>1.1.1 存储介质</li>
<li>1.1.3 消息的存储和发送</li>
<li>1.1.4 消息存储结构</li>
<li>1.1.5 刷盘机制</li>
</ul>
</li>
<li>1.2 高可用性机制<ul>
<li>1.2.1 消息消费高可用</li>
<li>1.2.2 消息发送高可用</li>
<li>1.2.3 消息主从复制</li>
</ul>
</li>
<li>1.3 负载均衡<ul>
<li>1.3.1 Producer负载均衡</li>
<li>1.3.2 Consumer负载均衡</li>
</ul>
</li>
<li>1.4 消息重试<ul>
<li>1.4.1 顺序消息的重试</li>
<li>1.4.2 无序消息的重试</li>
</ul>
</li>
<li>1.5 死信队列<ul>
<li>1.5.1 死信特性</li>
<li>1.5.2 查看死信信息</li>
</ul>
</li>
<li>1.6 消费幂等<ul>
<li>1.6.1 消费幂等的必要性</li>
<li>1.6.2 处理方式</li>
</ul>
</li>
<li>2.1 环境搭建<ul>
<li>2.1.1 源码拉取</li>
<li>2.1.3 调试</li>
</ul>
</li>
<li>2.2 NameServer<ul>
<li>2.2.1 架构设计</li>
<li>2.2.2 启动流程</li>
<li>2.2.3 路由管理</li>
<li>2.2.4 小结</li>
</ul>
</li>
<li>2.3 Producer<ul>
<li>2.3.2 启动流程</li>
<li>2.3.3 消息发送</li>
<li>2.3.4 批量消息发送</li>
</ul>
</li>
<li>2.4 消息存储<ul>
<li>2.4.2 消息存储流程</li>
<li>2.4.3 存储文件</li>
<li>2.4.4 存储文件内存映射</li>
<li>2.4.5 实时更新消息消费队列与索引文件</li>
<li>2.4.6 消息队列和索引文件恢复</li>
<li>2.4.7 刷盘机制</li>
<li>2.4.8 过期文件删除机制</li>
<li>2.4.9 小结</li>
</ul>
</li>
<li>2.5 Consumer<ul>
<li>2.5.1 消息消费概述</li>
<li>2.5.3 消费者启动流程</li>
<li>2.5.4 消息拉取</li>
<li>2.5.5 消息队列负载与重新分布机制</li>
<li>2.5.6 消息消费过程</li>
<li>2.5.7 定时消息机制</li>
<li>2.5.8 顺序消息</li>
<li>2.5.9 小结</li>
</ul>
</li>
</ul>
<h1 id="1-高级功能"><a href="#1-高级功能" class="headerlink" title="1. 高级功能"></a>1. 高级功能</h1><h2 id="1-1-消息存储"><a href="#1-1-消息存储" class="headerlink" title="1.1 消息存储"></a>1.1 消息存储</h2><p>分布式队列因为有高可靠性的要求，所以数据要进行持久化存储。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F.png"
                      alt="img"
                ></p>
<ol>
<li>消息生成者发送消息</li>
<li>MQ收到消息，将消息进行持久化，在存储中新增一条记录</li>
<li>返回ACK给生产者</li>
<li>MQ push 消息给对应的消费者，然后等待消费者返回ACK</li>
<li>如果消息消费者在指定时间内成功返回ack，那么MQ认为消息消费成功，在存储中删除消息，即执行第6步；如果MQ在指定时间内没有收到ACK，则认为消息消费失败，会尝试重新push消息,重复执行4、5、6步骤</li>
<li>MQ删除消息</li>
</ol>
<h3 id="1-1-1-存储介质"><a href="#1-1-1-存储介质" class="headerlink" title="1.1.1 存储介质"></a>1.1.1 存储介质</h3><ul>
<li>关系型数据库DB</li>
</ul>
<p>Apache下开源的另外一款MQ—ActiveMQ（默认采用的KahaDB做消息存储）可选用JDBC的方式来做消息持久化，通过简单的xml配置信息即可实现JDBC消息存储。由于，普通关系型数据库（如Mysql）在单表数据量达到千万级别的情况下，其IO读写性能往往会出现瓶颈。在可靠性方面，该种方案非常依赖DB，如果一旦DB出现故障，则MQ的消息就无法落盘存储会导致线上故障</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/MySQL.png"
                      alt="img"
                ></p>
<ul>
<li><p>文件系统</p>
<p>目前业界较为常用的几款产品（RocketMQ&#x2F;Kafka&#x2F;RabbitMQ）均采用的是消息刷盘至所部署虚拟机&#x2F;物理机的文件系统来做持久化（刷盘一般可以分为异步刷盘和同步刷盘两种模式）。消息刷盘为消息存储提供了一种高效率、高可靠性和高性能的数据持久化方式。除非部署MQ机器本身或是本地磁盘挂了，否则一般是不会出现无法持久化的故障问题。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E7%A3%81%E7%9B%98.png"
                      alt="img"
                ></p>
</li>
</ul>
<p>###1.1.2 性能对比</p>
<p>文件系统&gt;关系型数据库DB</p>
<h3 id="1-1-3-消息的存储和发送"><a href="#1-1-3-消息的存储和发送" class="headerlink" title="1.1.3 消息的存储和发送"></a>1.1.3 消息的存储和发送</h3><h4 id="1）消息存储"><a href="#1）消息存储" class="headerlink" title="1）消息存储"></a>1）消息存储</h4><p>磁盘如果使用得当，磁盘的速度完全可以匹配上网络 的数据传输速度。目前的高性能磁盘，顺序写速度可以达到600MB&#x2F;s， 超过了一般网卡的传输速度。但是磁盘随机写的速度只有大概100KB&#x2F;s，和顺序写的性能相差6000倍！因为有如此巨大的速度差别，好的消息队列系统会比普通的消息队列系统速度快多个数量级。RocketMQ的消息用顺序写,保证了消息存储的速度。</p>
<p>####2）消息发送</p>
<p>Linux操作系统分为【用户态】和【内核态】，文件操作、网络操作需要涉及这两种形态的切换，免不了进行数据复制。</p>
<p>一台服务器 把本机磁盘文件的内容发送到客户端，一般分为两个步骤：</p>
<p>1）read；读取本地文件内容；</p>
<p>2）write；将读取的内容通过网络发送出去。</p>
<p>这两个看似简单的操作，实际进行了4 次数据复制，分别是：</p>
<ol>
<li>从磁盘复制数据到内核态内存；</li>
<li>从内核态内存复 制到用户态内存；</li>
<li>然后从用户态 内存复制到网络驱动的内核态内存；</li>
<li>最后是从网络驱动的内核态内存复 制到网卡中进行传输。</li>
</ol>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E5%92%8C%E7%BD%91%E7%BB%9C%E6%93%8D%E4%BD%9C.png"
                      alt="img"
                >通过使用mmap的方式，可以省去向用户态的内存复制，提高速度。这种机制在Java中是通过MappedByteBuffer实现的</p>
<p>RocketMQ充分利用了上述特性，也就是所谓的“零拷贝”技术，提高消息存盘和网络发送的速度。</p>
<blockquote>
<p>这里需要注意的是，采用MappedByteBuffer这种内存映射的方式有几个限制，其中之一是一次只能映射1.5~2G 的文件至用户态的虚拟内存，这也是为何RocketMQ默认设置单个CommitLog日志数据文件为1G的原因了</p>
</blockquote>
<h3 id="1-1-4-消息存储结构"><a href="#1-1-4-消息存储结构" class="headerlink" title="1.1.4 消息存储结构"></a>1.1.4 消息存储结构</h3><p>RocketMQ消息的存储是由ConsumeQueue和CommitLog配合完成 的，消息真正的物理存储文件是CommitLog，ConsumeQueue是消息的逻辑队列，类似数据库的索引文件，存储的是指向物理存储的地址。每 个Topic下的每个Message Queue都有一个对应的ConsumeQueue文件。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84.png"
                      alt="img"
                ></p>
<ul>
<li>CommitLog：存储消息的元数据</li>
<li>ConsumerQueue：存储消息在CommitLog的索引</li>
<li>IndexFile：为了消息查询提供了一种通过key或时间区间来查询消息的方法，这种通过IndexFile来查找消息的方法不影响发送与消费消息的主流程</li>
</ul>
<h3 id="1-1-5-刷盘机制"><a href="#1-1-5-刷盘机制" class="headerlink" title="1.1.5 刷盘机制"></a>1.1.5 刷盘机制</h3><p>RocketMQ的消息是存储到磁盘上的，这样既能保证断电后恢复， 又可以让存储的消息量超出内存的限制。RocketMQ为了提高性能，会尽可能地保证磁盘的顺序写。消息在通过Producer写入RocketMQ的时 候，有两种写磁盘方式，分布式同步刷盘和异步刷盘。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%90%8C%E6%AD%A5%E5%88%B7%E7%9B%98%E5%92%8C%E5%BC%82%E6%AD%A5%E5%88%B7%E7%9B%98.png"
                      alt="img"
                ></p>
<h4 id="1）同步刷盘"><a href="#1）同步刷盘" class="headerlink" title="1）同步刷盘"></a>1）同步刷盘</h4><p>在返回写成功状态时，消息已经被写入磁盘。具体流程是，消息写入内存的PAGECACHE后，立刻通知刷盘线程刷盘， 然后等待刷盘完成，刷盘线程执行完成后唤醒等待的线程，返回消息写 成功的状态。</p>
<h4 id="2）异步刷盘"><a href="#2）异步刷盘" class="headerlink" title="2）异步刷盘"></a>2）异步刷盘</h4><p>在返回写成功状态时，消息可能只是被写入了内存的PAGECACHE，写操作的返回快，吞吐量大；当内存里的消息量积累到一定程度时，统一触发写磁盘动作，快速写入。</p>
<h4 id="3）配置"><a href="#3）配置" class="headerlink" title="3）配置"></a>3）配置</h4><p>同步刷盘还是异步刷盘，都是通过Broker配置文件里的flushDiskType 参数设置的，这个参数被配置成SYNC_FLUSH、ASYNC_FLUSH中的 一个。</p>
<h2 id="1-2-高可用性机制"><a href="#1-2-高可用性机制" class="headerlink" title="1.2 高可用性机制"></a>1.2 高可用性机制</h2><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/RocketMQ%E8%A7%92%E8%89%B2.jpg"
                      alt="img"
                ></p>
<p>RocketMQ分布式集群是通过Master和Slave的配合达到高可用性的。</p>
<p>Master和Slave的区别：在Broker的配置文件中，参数 brokerId的值为0表明这个Broker是Master，大于0表明这个Broker是 Slave，同时brokerRole参数也会说明这个Broker是Master还是Slave。</p>
<p>Master角色的Broker支持读和写，Slave角色的Broker仅支持读，也就是 Producer只能和Master角色的Broker连接写入消息；Consumer可以连接 Master角色的Broker，也可以连接Slave角色的Broker来读取消息。</p>
<h3 id="1-2-1-消息消费高可用"><a href="#1-2-1-消息消费高可用" class="headerlink" title="1.2.1 消息消费高可用"></a>1.2.1 消息消费高可用</h3><p>在Consumer的配置文件中，并不需要设置是从Master读还是从Slave 读，当Master不可用或者繁忙的时候，Consumer会被自动切换到从Slave 读。有了自动切换Consumer这种机制，当一个Master角色的机器出现故障后，Consumer仍然可以从Slave读取消息，不影响Consumer程序。这就达到了消费端的高可用性。</p>
<h3 id="1-2-2-消息发送高可用"><a href="#1-2-2-消息发送高可用" class="headerlink" title="1.2.2 消息发送高可用"></a>1.2.2 消息发送高可用</h3><p>在创建Topic的时候，把Topic的多个Message Queue创建在多个Broker组上（相同Broker名称，不同 brokerId的机器组成一个Broker组），这样当一个Broker组的Master不可 用后，其他组的Master仍然可用，Producer仍然可以发送消息。 RocketMQ目前还不支持把Slave自动转成Master，如果机器资源不足， 需要把Slave转成Master，则要手动停止Slave角色的Broker，更改配置文 件，用新的配置文件启动Broker。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E9%AB%98%E5%8F%AF%E7%94%A8%E8%AE%BE%E8%AE%A1.jpg"
                      alt="img"
                ></p>
<h3 id="1-2-3-消息主从复制"><a href="#1-2-3-消息主从复制" class="headerlink" title="1.2.3 消息主从复制"></a>1.2.3 消息主从复制</h3><p>如果一个Broker组有Master和Slave，消息需要从Master复制到Slave 上，有同步和异步两种复制方式。</p>
<p>####1）同步复制</p>
<p>同步复制方式是等Master和Slave均写 成功后才反馈给客户端写成功状态；</p>
<p>在同步复制方式下，如果Master出故障， Slave上有全部的备份数据，容易恢复，但是同步复制会增大数据写入 延迟，降低系统吞吐量。</p>
<p>####2）异步复制</p>
<p>异步复制方式是只要Master写成功 即可反馈给客户端写成功状态。</p>
<p>在异步复制方式下，系统拥有较低的延迟和较高的吞吐量，但是如果Master出了故障，有些数据因为没有被写 入Slave，有可能会丢失；</p>
<p>####3）配置</p>
<p>同步复制和异步复制是通过Broker配置文件里的brokerRole参数进行设置的，这个参数可以被设置成ASYNC_MASTER、 SYNC_MASTER、SLAVE三个值中的一个。</p>
<p>####4）总结</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%A4%8D%E5%88%B6%E5%88%B7%E7%9B%98.png"
                      alt="img"
                ></p>
<p>实际应用中要结合业务场景，合理设置刷盘方式和主从复制方式， 尤其是SYNC_FLUSH方式，由于频繁地触发磁盘写动作，会明显降低 性能。通常情况下，应该把Master和Save配置成ASYNC_FLUSH的刷盘 方式，主从之间配置成SYNC_MASTER的复制方式，这样即使有一台 机器出故障，仍然能保证数据不丢，是个不错的选择。</p>
<h2 id="1-3-负载均衡"><a href="#1-3-负载均衡" class="headerlink" title="1.3 负载均衡"></a>1.3 负载均衡</h2><h3 id="1-3-1-Producer负载均衡"><a href="#1-3-1-Producer负载均衡" class="headerlink" title="1.3.1 Producer负载均衡"></a>1.3.1 Producer负载均衡</h3><p>Producer端，每个实例在发消息的时候，默认会轮询所有的message queue发送，以达到让消息平均落在不同的queue上。而由于queue可以散落在不同的broker，所以消息就发送到不同的broker下，如下图：</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/producer%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1.png"
                      alt="img"
                ></p>
<p>图中箭头线条上的标号代表顺序，发布方会把第一条消息发送至 Queue 0，然后第二条消息发送至 Queue 1，以此类推。</p>
<h3 id="1-3-2-Consumer负载均衡"><a href="#1-3-2-Consumer负载均衡" class="headerlink" title="1.3.2 Consumer负载均衡"></a>1.3.2 Consumer负载均衡</h3><h4 id="1）集群模式"><a href="#1）集群模式" class="headerlink" title="1）集群模式"></a>1）集群模式</h4><p>在集群消费模式下，每条消息只需要投递到订阅这个topic的Consumer Group下的一个实例即可。RocketMQ采用主动拉取的方式拉取并消费消息，在拉取的时候需要明确指定拉取哪一条message queue。</p>
<p>而每当实例的数量有变更，都会触发一次所有实例的负载均衡，这时候会按照queue的数量和实例的数量平均分配queue给每个实例。</p>
<p>默认的分配算法是AllocateMessageQueueAveragely，如下图：</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/consumer%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1.png"
                      alt="img"
                ></p>
<p>还有另外一种平均的算法是AllocateMessageQueueAveragelyByCircle，也是平均分摊每一条queue，只是以环状轮流分queue的形式，如下图：</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/consumer%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A12.png"
                      alt="img"
                ></p>
<p>需要注意的是，集群模式下，queue都是只允许分配只一个实例，这是由于如果多个实例同时消费一个queue的消息，由于拉取哪些消息是consumer主动控制的，那样会导致同一个消息在不同的实例下被消费多次，所以算法上都是一个queue只分给一个consumer实例，一个consumer实例可以允许同时分到不同的queue。</p>
<p>通过增加consumer实例去分摊queue的消费，可以起到水平扩展的消费能力的作用。而有实例下线的时候，会重新触发负载均衡，这时候原来分配到的queue将分配到其他实例上继续消费。</p>
<p>但是如果consumer实例的数量比message queue的总数量还多的话，多出来的consumer实例将无法分到queue，也就无法消费到消息，也就无法起到分摊负载的作用了。所以需要控制让queue的总数量大于等于consumer的数量。</p>
<p>####2）广播模式</p>
<p>由于广播模式下要求一条消息需要投递到一个消费组下面所有的消费者实例，所以也就没有消息被分摊消费的说法。</p>
<p>在实现上，其中一个不同就是在consumer分配queue的时候，所有consumer都分到所有的queue。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/consumer%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A13.png"
                      alt="img"
                ></p>
<h2 id="1-4-消息重试"><a href="#1-4-消息重试" class="headerlink" title="1.4 消息重试"></a>1.4 消息重试</h2><h3 id="1-4-1-顺序消息的重试"><a href="#1-4-1-顺序消息的重试" class="headerlink" title="1.4.1 顺序消息的重试"></a>1.4.1 顺序消息的重试</h3><p>对于顺序消息，当消费者消费消息失败后，消息队列 RocketMQ 会自动不断进行消息重试（每次间隔时间为 1 秒），这时，应用会出现消息消费被阻塞的情况。因此，在使用顺序消息时，务必保证应用能够及时监控并处理消费失败的情况，避免阻塞现象的发生。</p>
<h3 id="1-4-2-无序消息的重试"><a href="#1-4-2-无序消息的重试" class="headerlink" title="1.4.2 无序消息的重试"></a>1.4.2 无序消息的重试</h3><p>对于无序消息（普通、定时、延时、事务消息），当消费者消费消息失败时，您可以通过设置返回状态达到消息重试的结果。</p>
<p>无序消息的重试只针对集群消费方式生效；广播方式不提供失败重试特性，即消费失败后，失败消息不再重试，继续消费新的消息。</p>
<h4 id="1）重试次数"><a href="#1）重试次数" class="headerlink" title="1）重试次数"></a>1）重试次数</h4><p>消息队列 RocketMQ 默认允许每条消息最多重试 16 次，每次重试的间隔时间如下：</p>
<table>
<thead>
<tr>
<th align="center">第几次重试</th>
<th align="center">与上次重试的间隔时间</th>
<th align="center">第几次重试</th>
<th align="center">与上次重试的间隔时间</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">10 秒</td>
<td align="center">9</td>
<td align="center">7 分钟</td>
</tr>
<tr>
<td align="center">2</td>
<td align="center">30 秒</td>
<td align="center">10</td>
<td align="center">8 分钟</td>
</tr>
<tr>
<td align="center">3</td>
<td align="center">1 分钟</td>
<td align="center">11</td>
<td align="center">9 分钟</td>
</tr>
<tr>
<td align="center">4</td>
<td align="center">2 分钟</td>
<td align="center">12</td>
<td align="center">10 分钟</td>
</tr>
<tr>
<td align="center">5</td>
<td align="center">3 分钟</td>
<td align="center">13</td>
<td align="center">20 分钟</td>
</tr>
<tr>
<td align="center">6</td>
<td align="center">4 分钟</td>
<td align="center">14</td>
<td align="center">30 分钟</td>
</tr>
<tr>
<td align="center">7</td>
<td align="center">5 分钟</td>
<td align="center">15</td>
<td align="center">1 小时</td>
</tr>
<tr>
<td align="center">8</td>
<td align="center">6 分钟</td>
<td align="center">16</td>
<td align="center">2 小时</td>
</tr>
</tbody></table>
<p>如果消息重试 16 次后仍然失败，消息将不再投递。如果严格按照上述重试时间间隔计算，某条消息在一直消费失败的前提下，将会在接下来的 4 小时 46 分钟之内进行 16 次重试，超过这个时间范围消息将不再重试投递。</p>
<p><strong>注意：</strong> 一条消息无论重试多少次，这些重试消息的 Message ID 不会改变。</p>
<h4 id="2）配置方式"><a href="#2）配置方式" class="headerlink" title="2）配置方式"></a>2）配置方式</h4><p>消费失败后，重试配置方式</p>
<p>集群消费方式下，消息消费失败后期望消息重试，需要在消息监听器接口的实现中明确进行配置（三种方式任选一种）：</p>
<ul>
<li>返回 Action.ReconsumeLater （推荐）</li>
<li>返回 Null</li>
<li>抛出异常</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageListenerImpl</span> <span class="keyword">implements</span> <span class="title class_">MessageListener</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Action <span class="title function_">consume</span><span class="params">(Message message, ConsumeContext context)</span> &#123;</span><br><span class="line">        <span class="comment">//处理消息</span></span><br><span class="line">        doConsumeMessage(message);</span><br><span class="line">        <span class="comment">//方式1：返回 Action.ReconsumeLater，消息将重试</span></span><br><span class="line">        <span class="keyword">return</span> Action.ReconsumeLater;</span><br><span class="line">        <span class="comment">//方式2：返回 null，消息将重试</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="comment">//方式3：直接抛出异常， 消息将重试</span></span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;Consumer Message exceotion&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>消费失败后，不重试配置方式</strong></p>
<p>集群消费方式下，消息失败后期望消息不重试，需要捕获消费逻辑中可能抛出的异常，最终返回 Action.CommitMessage，此后这条消息将不会再重试。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageListenerImpl</span> <span class="keyword">implements</span> <span class="title class_">MessageListener</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Action <span class="title function_">consume</span><span class="params">(Message message, ConsumeContext context)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            doConsumeMessage(message);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            <span class="comment">//捕获消费逻辑中的所有异常，并返回 Action.CommitMessage;</span></span><br><span class="line">            <span class="keyword">return</span> Action.CommitMessage;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//消息处理正常，直接返回 Action.CommitMessage;</span></span><br><span class="line">        <span class="keyword">return</span> Action.CommitMessage;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>自定义消息最大重试次数</strong></p>
<p>消息队列 RocketMQ 允许 Consumer 启动的时候设置最大重试次数，重试时间间隔将按照如下策略：</p>
<ul>
<li>最大重试次数小于等于 16 次，则重试时间间隔同上表描述。</li>
<li>最大重试次数大于 16 次，超过 16 次的重试时间间隔均为每次 2 小时。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Properties</span> <span class="variable">properties</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line"><span class="comment">//配置对应 Group ID 的最大消息重试次数为 20 次</span></span><br><span class="line">properties.put(PropertyKeyConst.MaxReconsumeTimes,<span class="string">&quot;20&quot;</span>);</span><br><span class="line"><span class="type">Consumer</span> <span class="variable">consumer</span> <span class="operator">=</span>ONSFactory.createConsumer(properties);</span><br></pre></td></tr></table></figure>



<blockquote>
<p>注意：</p>
</blockquote>
<ul>
<li>消息最大重试次数的设置对相同 Group ID 下的所有 Consumer 实例有效。</li>
<li>如果只对相同 Group ID 下两个 Consumer 实例中的其中一个设置了 MaxReconsumeTimes，那么该配置对两个 Consumer 实例均生效。</li>
<li>配置采用覆盖的方式生效，即最后启动的 Consumer 实例会覆盖之前的启动实例的配置</li>
</ul>
<p><strong>获取消息重试次数</strong></p>
<p>消费者收到消息后，可按照如下方式获取消息的重试次数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageListenerImpl</span> <span class="keyword">implements</span> <span class="title class_">MessageListener</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Action <span class="title function_">consume</span><span class="params">(Message message, ConsumeContext context)</span> &#123;</span><br><span class="line">        <span class="comment">//获取消息的重试次数</span></span><br><span class="line">        System.out.println(message.getReconsumeTimes());</span><br><span class="line">        <span class="keyword">return</span> Action.CommitMessage;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="1-5-死信队列"><a href="#1-5-死信队列" class="headerlink" title="1.5 死信队列"></a>1.5 死信队列</h2><p>当一条消息初次消费失败，消息队列 RocketMQ 会自动进行消息重试；达到最大重试次数后，若消费依然失败，则表明消费者在正常情况下无法正确地消费该消息，此时，消息队列 RocketMQ 不会立刻将消息丢弃，而是将其发送到该消费者对应的特殊队列中。</p>
<p>在消息队列 RocketMQ 中，这种正常情况下无法被消费的消息称为死信消息（Dead-Letter Message），存储死信消息的特殊队列称为死信队列（Dead-Letter Queue）。</p>
<h3 id="1-5-1-死信特性"><a href="#1-5-1-死信特性" class="headerlink" title="1.5.1 死信特性"></a>1.5.1 死信特性</h3><p>死信消息具有以下特性</p>
<ul>
<li>不会再被消费者正常消费。</li>
<li>有效期与正常消息相同，均为 3 天，3 天后会被自动删除。因此，请在死信消息产生后的 3 天内及时处理。</li>
</ul>
<p>死信队列具有以下特性：</p>
<ul>
<li>一个死信队列对应一个 Group ID， 而不是对应单个消费者实例。</li>
<li>如果一个 Group ID 未产生死信消息，消息队列 RocketMQ 不会为其创建相应的死信队列。</li>
<li>一个死信队列包含了对应 Group ID 产生的所有死信消息，不论该消息属于哪个 Topic。</li>
</ul>
<h3 id="1-5-2-查看死信信息"><a href="#1-5-2-查看死信信息" class="headerlink" title="1.5.2 查看死信信息"></a>1.5.2 查看死信信息</h3><ol>
<li>在控制台查询出现死信队列的主题信息</li>
</ol>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%E4%B8%BB%E9%A2%98.png"
                      alt="img"
                ></p>
<ol>
<li>在消息界面根据主题查询死信消息</li>
</ol>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%E4%B8%BB%E9%A2%982.png"
                      alt="img"
                ></p>
<ol>
<li>选择重新发送消息</li>
</ol>
<p>一条消息进入死信队列，意味着某些因素导致消费者无法正常消费该消息，因此，通常需要您对其进行特殊处理。排查可疑因素并解决问题后，可以在消息队列 RocketMQ 控制台重新发送该消息，让消费者重新消费一次。</p>
<h2 id="1-6-消费幂等"><a href="#1-6-消费幂等" class="headerlink" title="1.6 消费幂等"></a>1.6 消费幂等</h2><p>消息队列 RocketMQ 消费者在接收到消息以后，有必要根据业务上的唯一 Key 对消息做幂等处理的必要性。</p>
<h3 id="1-6-1-消费幂等的必要性"><a href="#1-6-1-消费幂等的必要性" class="headerlink" title="1.6.1 消费幂等的必要性"></a>1.6.1 消费幂等的必要性</h3><p>在互联网应用中，尤其在网络不稳定的情况下，消息队列 RocketMQ 的消息有可能会出现重复，这个重复简单可以概括为以下情况：</p>
<ul>
<li><p>发送时消息重复</p>
<p>当一条消息已被成功发送到服务端并完成持久化，此时出现了网络闪断或者客户端宕机，导致服务端对客户端应答失败。 如果此时生产者意识到消息发送失败并尝试再次发送消息，消费者后续会收到两条内容相同并且 Message ID 也相同的消息。</p>
</li>
<li><p>投递时消息重复</p>
<p>消息消费的场景下，消息已投递到消费者并完成业务处理，当客户端给服务端反馈应答的时候网络闪断。 为了保证消息至少被消费一次，消息队列 RocketMQ 的服务端将在网络恢复后再次尝试投递之前已被处理过的消息，消费者后续会收到两条内容相同并且 Message ID 也相同的消息。</p>
</li>
<li><p>负载均衡时消息重复（包括但不限于网络抖动、Broker 重启以及订阅方应用重启）</p>
<p>当消息队列 RocketMQ 的 Broker 或客户端重启、扩容或缩容时，会触发 Rebalance，此时消费者可能会收到重复消息。</p>
</li>
</ul>
<h3 id="1-6-2-处理方式"><a href="#1-6-2-处理方式" class="headerlink" title="1.6.2 处理方式"></a>1.6.2 处理方式</h3><p>因为 Message ID 有可能出现冲突（重复）的情况，所以真正安全的幂等处理，不建议以 Message ID 作为处理依据。 最好的方式是以业务唯一标识作为幂等处理的关键依据，而业务的唯一标识可以通过消息 Key 进行设置：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>();</span><br><span class="line">message.setKey(<span class="string">&quot;ORDERID_100&quot;</span>);</span><br><span class="line"><span class="type">SendResult</span> <span class="variable">sendResult</span> <span class="operator">=</span> producer.send(message);</span><br></pre></td></tr></table></figure>



<p>订阅方收到消息时可以根据消息的 Key 进行幂等处理：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">consumer.subscribe(<span class="string">&quot;ons_test&quot;</span>, <span class="string">&quot;*&quot;</span>, <span class="keyword">new</span> <span class="title class_">MessageListener</span>() &#123;</span><br><span class="line">    <span class="keyword">public</span> Action <span class="title function_">consume</span><span class="params">(Message message, ConsumeContext context)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> message.getKey()</span><br><span class="line">        <span class="comment">// 根据业务唯一标识的 key 做幂等处理</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<h1 id="2-源码分析"><a href="#2-源码分析" class="headerlink" title="2. 源码分析"></a>2. 源码分析</h1><h2 id="2-1-环境搭建"><a href="#2-1-环境搭建" class="headerlink" title="2.1 环境搭建"></a>2.1 环境搭建</h2><p>依赖工具</p>
<ul>
<li>JDK ：1.8+</li>
<li>Maven</li>
<li>IntelliJ IDEA</li>
</ul>
<h3 id="2-1-1-源码拉取"><a href="#2-1-1-源码拉取" class="headerlink" title="2.1.1 源码拉取"></a>2.1.1 源码拉取</h3><p>从官方仓库 <a class="link"   target="_blank" rel="noopener" href="https://github.com/apache/rocketmq" >https://github.com/apache/rocketmq<i class="fas fa-external-link-alt"></i></a> <code>clone</code>或者<code>download</code>源码。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%811.png"
                      alt="img"
                ></p>
<p><strong>源码目录结构：</strong></p>
<ul>
<li>broker: broker 模块（broke 启动进程）</li>
<li>client ：消息客户端，包含消息生产者、消息消费者相关类</li>
<li>common ：公共包</li>
<li>dev ：开发者信息（非源代码）</li>
<li>distribution ：部署实例文件夹（非源代码）</li>
<li>example: RocketMQ 例代码</li>
<li>filter ：消息过滤相关基础类</li>
<li>filtersrv：消息过滤服务器实现相关类（Filter启动进程）</li>
<li>logappender：日志实现相关类</li>
<li>namesrv：NameServer实现相关类（NameServer启动进程）</li>
<li>openmessageing：消息开放标准</li>
<li>remoting：远程通信模块，给予Netty</li>
<li>srcutil：服务工具类</li>
<li>store：消息存储实现相关类</li>
<li>style：checkstyle相关实现</li>
<li>test：测试相关类</li>
<li>tools：工具类，监控命令相关实现类</li>
</ul>
<p>###2.1.2 导入IDEA</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%812.png"
                      alt="img"
                ></p>
<p><strong>执行安装</strong></p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">clean install -Dmaven.test.skip=<span class="literal">true</span></span><br></pre></td></tr></table></figure>



<h3 id="2-1-3-调试"><a href="#2-1-3-调试" class="headerlink" title="2.1.3 调试"></a>2.1.3 调试</h3><p>创建<code>conf</code>配置文件夹,从<code>distribution</code>拷贝<code>broker.conf</code>和<code>logback_broker.xml</code>和<code>logback_namesrv.xml</code></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%816.png"
                      alt="img"
                ></p>
<h4 id="1）启动NameServer"><a href="#1）启动NameServer" class="headerlink" title="1）启动NameServer"></a>1）启动NameServer</h4><ul>
<li>展开namesrv模块，右键NamesrvStartup.java</li>
</ul>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%813.png"
                      alt="img"
                ></p>
<ul>
<li>配置<strong>ROCKETMQ_HOME</strong></li>
</ul>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%814.png"
                      alt="img"
                ></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%815.png"
                      alt="img"
                ></p>
<ul>
<li><p>重新启动</p>
<p>控制台打印结果</p>
</li>
</ul>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">The Name Server boot success. serializeType=JSON</span><br></pre></td></tr></table></figure>

<p>1</p>
<h4 id="2）启动Broker"><a href="#2）启动Broker" class="headerlink" title="2）启动Broker"></a>2）启动Broker</h4><ul>
<li><code>broker.conf</code>配置文件内容</li>
</ul>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">brokerClusterName</span> = <span class="string">DefaultCluster</span></span><br><span class="line"><span class="attr">brokerName</span> = <span class="string">broker-a</span></span><br><span class="line"><span class="attr">brokerId</span> = <span class="string">0</span></span><br><span class="line"><span class="comment"># namesrvAddr地址</span></span><br><span class="line"><span class="attr">namesrvAddr</span>=<span class="string">127.0.0.1:9876</span></span><br><span class="line"><span class="attr">deleteWhen</span> = <span class="string">04</span></span><br><span class="line"><span class="attr">fileReservedTime</span> = <span class="string">48</span></span><br><span class="line"><span class="attr">brokerRole</span> = <span class="string">ASYNC_MASTER</span></span><br><span class="line"><span class="attr">flushDiskType</span> = <span class="string">ASYNC_FLUSH</span></span><br><span class="line"><span class="attr">autoCreateTopicEnable</span>=<span class="string">true</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment"># 存储路径</span></span><br><span class="line"><span class="attr">storePathRootDir</span>=<span class="string">E:\\RocketMQ\\data\\rocketmq\\dataDir</span></span><br><span class="line"><span class="comment"># commitLog路径</span></span><br><span class="line"><span class="attr">storePathCommitLog</span>=<span class="string">E:\\RocketMQ\\data\\rocketmq\\dataDir\\commitlog</span></span><br><span class="line"><span class="comment"># 消息队列存储路径</span></span><br><span class="line"><span class="attr">storePathConsumeQueue</span>=<span class="string">E:\\RocketMQ\\data\\rocketmq\\dataDir\\consumequeue</span></span><br><span class="line"><span class="comment"># 消息索引存储路径</span></span><br><span class="line"><span class="attr">storePathIndex</span>=<span class="string">E:\\RocketMQ\\data\\rocketmq\\dataDir\\index</span></span><br><span class="line"><span class="comment"># checkpoint文件路径</span></span><br><span class="line"><span class="attr">storeCheckpoint</span>=<span class="string">E:\\RocketMQ\\data\\rocketmq\\dataDir\\checkpoint</span></span><br><span class="line"><span class="comment"># abort文件存储路径</span></span><br><span class="line"><span class="attr">abortFile</span>=<span class="string">E:\\RocketMQ\\data\\rocketmq\\dataDir\\abort</span></span><br></pre></td></tr></table></figure>



<ul>
<li>创建数据文件夹<code>dataDir</code></li>
<li>启动<code>BrokerStartup</code>,配置<code>broker.conf</code>和<code>ROCKETMQ_HOME</code></li>
</ul>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%817.png"
                      alt="img"
                ></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%BA%90%E7%A0%818.png"
                      alt="img"
                ></p>
<h4 id="3）发送消息"><a href="#3）发送消息" class="headerlink" title="3）发送消息"></a>3）发送消息</h4><ul>
<li>进入example模块的<code>org.apache.rocketmq.example.quickstart</code></li>
<li>指定Namesrv地址</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DefaultMQProducer</span> <span class="variable">producer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQProducer</span>(<span class="string">&quot;please_rename_unique_group_name&quot;</span>);</span><br><span class="line">producer.setNamesrvAddr(<span class="string">&quot;127.0.0.1:9876&quot;</span>);</span><br></pre></td></tr></table></figure>



<ul>
<li>运行<code>main</code>方法，发送消息</li>
</ul>
<h4 id="4）消费消息"><a href="#4）消费消息" class="headerlink" title="4）消费消息"></a>4）消费消息</h4><ul>
<li>进入example模块的<code>org.apache.rocketmq.example.quickstart</code></li>
<li>指定Namesrv地址</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">DefaultMQPushConsumer</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DefaultMQPushConsumer</span>(<span class="string">&quot;please_rename_unique_group_name_4&quot;</span>);</span><br><span class="line">consumer.setNamesrvAddr(<span class="string">&quot;127.0.0.1:9876&quot;</span>);</span><br></pre></td></tr></table></figure>



<ul>
<li>运行<code>main</code>方法，消费消息</li>
</ul>
<h2 id="2-2-NameServer"><a href="#2-2-NameServer" class="headerlink" title="2.2 NameServer"></a>2.2 NameServer</h2><h3 id="2-2-1-架构设计"><a href="#2-2-1-架构设计" class="headerlink" title="2.2.1 架构设计"></a>2.2.1 架构设计</h3><p>消息中间件的设计思路一般是基于主题订阅发布的机制，消息生产者（Producer）发送某一个主题到消息服务器，消息服务器负责将消息持久化存储，消息消费者（Consumer）订阅该兴趣的主题，消息服务器根据订阅信息（路由信息）将消息推送到消费者（Push模式）或者消费者主动向消息服务器拉去（Pull模式），从而实现消息生产者与消息消费者解耦。为了避免消息服务器的单点故障导致的整个系统瘫痪，通常会部署多台消息服务器共同承担消息的存储。那消息生产者如何知道消息要发送到哪台消息服务器呢？如果某一台消息服务器宕机了，那么消息生产者如何在不重启服务情况下感知呢？</p>
<p>NameServer就是为了解决以上问题设计的。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/RocketMQ%E8%A7%92%E8%89%B2.jpg"
                      alt="img"
                ></p>
<p>Broker消息服务器在启动的时向所有NameServer注册，消息生产者（Producer）在发送消息时之前先从NameServer获取Broker服务器地址列表，然后根据负载均衡算法从列表中选择一台服务器进行发送。NameServer与每台Broker保持长连接，并间隔30S检测Broker是否存活，如果检测到Broker宕机，则从路由注册表中删除。但是路由变化不会马上通知消息生产者。这样设计的目的是为了降低NameServer实现的复杂度，在消息发送端提供容错机制保证消息发送的可用性。</p>
<p>NameServer本身的高可用是通过部署多台NameServer来实现，但彼此之间不通讯，也就是NameServer服务器之间在某一个时刻的数据并不完全相同，但这对消息发送并不会造成任何影响，这也是NameServer设计的一个亮点，总之，RocketMQ设计追求简单高效。</p>
<h3 id="2-2-2-启动流程"><a href="#2-2-2-启动流程" class="headerlink" title="2.2.2 启动流程"></a>2.2.2 启动流程</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/NameServer%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p>启动类：<code>org.apache.rocketmq.namesrv.NamesrvStartup</code></p>
<p>####步骤一</p>
<p>解析配置文件，填充NameServerConfig、NettyServerConfig属性值，并创建NamesrvController</p>
<p><em><strong>代码：NamesrvController#createNamesrvController</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建NamesrvConfig</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">NamesrvConfig</span> <span class="variable">namesrvConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NamesrvConfig</span>();</span><br><span class="line"><span class="comment">//创建NettyServerConfig</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">NettyServerConfig</span> <span class="variable">nettyServerConfig</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NettyServerConfig</span>();</span><br><span class="line"><span class="comment">//设置启动端口号</span></span><br><span class="line">nettyServerConfig.setListenPort(<span class="number">9876</span>);</span><br><span class="line"><span class="comment">//解析启动-c参数</span></span><br><span class="line"><span class="keyword">if</span> (commandLine.hasOption(<span class="string">&#x27;c&#x27;</span>)) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">file</span> <span class="operator">=</span> commandLine.getOptionValue(<span class="string">&#x27;c&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (file != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">InputStream</span> <span class="variable">in</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BufferedInputStream</span>(<span class="keyword">new</span> <span class="title class_">FileInputStream</span>(file));</span><br><span class="line">        properties = <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">        properties.load(in);</span><br><span class="line">        MixAll.properties2Object(properties, namesrvConfig);</span><br><span class="line">        MixAll.properties2Object(properties, nettyServerConfig);</span><br><span class="line"></span><br><span class="line">        namesrvConfig.setConfigStorePath(file);</span><br><span class="line"></span><br><span class="line">        System.out.printf(<span class="string">&quot;load config properties file OK, %s%n&quot;</span>, file);</span><br><span class="line">        in.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//解析启动-p参数</span></span><br><span class="line"><span class="keyword">if</span> (commandLine.hasOption(<span class="string">&#x27;p&#x27;</span>)) &#123;</span><br><span class="line">    <span class="type">InternalLogger</span> <span class="variable">console</span> <span class="operator">=</span> InternalLoggerFactory.getLogger(LoggerName.NAMESRV_CONSOLE_NAME);</span><br><span class="line">    MixAll.printObjectProperties(console, namesrvConfig);</span><br><span class="line">    MixAll.printObjectProperties(console, nettyServerConfig);</span><br><span class="line">    System.exit(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//将启动参数填充到namesrvConfig,nettyServerConfig</span></span><br><span class="line">MixAll.properties2Object(ServerUtil.commandLine2Properties(commandLine), namesrvConfig);</span><br><span class="line"></span><br><span class="line"><span class="comment">//创建NameServerController</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">NamesrvController</span> <span class="variable">controller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NamesrvController</span>(namesrvConfig, nettyServerConfig);</span><br></pre></td></tr></table></figure>



<p><strong>NamesrvConfig属性</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">String</span> <span class="variable">rocketmqHome</span> <span class="operator">=</span> System.getProperty(MixAll.ROCKETMQ_HOME_PROPERTY, System.getenv(MixAll.ROCKETMQ_HOME_ENV));</span><br><span class="line"><span class="keyword">private</span> <span class="type">String</span> <span class="variable">kvConfigPath</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;user.home&quot;</span>) + File.separator + <span class="string">&quot;namesrv&quot;</span> + File.separator + <span class="string">&quot;kvConfig.json&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">String</span> <span class="variable">configStorePath</span> <span class="operator">=</span> System.getProperty(<span class="string">&quot;user.home&quot;</span>) + File.separator + <span class="string">&quot;namesrv&quot;</span> + File.separator + <span class="string">&quot;namesrv.properties&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">String</span> <span class="variable">productEnvName</span> <span class="operator">=</span> <span class="string">&quot;center&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">clusterTest</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">orderMessageEnable</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br></pre></td></tr></table></figure>



<p><strong>rocketmqHome：</strong>rocketmq主目录</p>
<p><strong>kvConfig：</strong>NameServer存储KV配置属性的持久化路径</p>
<p><strong>configStorePath：</strong>nameServer默认配置文件路径</p>
<p><strong>orderMessageEnable：</strong>是否支持顺序消息</p>
<p><strong>NettyServerConfig属性</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">listenPort</span> <span class="operator">=</span> <span class="number">8888</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverWorkerThreads</span> <span class="operator">=</span> <span class="number">8</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverCallbackExecutorThreads</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverSelectorThreads</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverOnewaySemaphoreValue</span> <span class="operator">=</span> <span class="number">256</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverAsyncSemaphoreValue</span> <span class="operator">=</span> <span class="number">64</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverChannelMaxIdleTimeSeconds</span> <span class="operator">=</span> <span class="number">120</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverSocketSndBufSize</span> <span class="operator">=</span> NettySystemConfig.socketSndbufSize;</span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">serverSocketRcvBufSize</span> <span class="operator">=</span> NettySystemConfig.socketRcvbufSize;</span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">serverPooledByteBufAllocatorEnable</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">useEpollNativeSelector</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br></pre></td></tr></table></figure>



<p><strong>listenPort：</strong>NameServer监听端口，该值默认会被初始化为9876 <strong>serverWorkerThreads：</strong>Netty业务线程池线程个数 <strong>serverCallbackExecutorThreads：</strong>Netty public任务线程池线程个数，Netty网络设计，根据业务类型会创建不同的线程池，比如处理消息发送、消息消费、心跳检测等。如果该业务类型未注册线程池，则由public线程池执行。 <strong>serverSelectorThreads：</strong>IO线程池个数，主要是NameServer、Broker端解析请求、返回相应的线程个数，这类线程主要是处理网路请求的，解析请求包，然后转发到各个业务线程池完成具体的操作，然后将结果返回给调用方; <strong>serverOnewaySemaphoreValue：</strong>send oneway消息请求并发读（Broker端参数）; <strong>serverAsyncSemaphoreValue：</strong>异步消息发送最大并发度; <strong>serverChannelMaxIdleTimeSeconds ：</strong>网络连接最大的空闲时间，默认120s。 <strong>serverSocketSndBufSize：</strong>网络socket发送缓冲区大小。 <strong>serverSocketRcvBufSize：</strong> 网络接收端缓存区大小。 <strong>serverPooledByteBufAllocatorEnable：</strong>ByteBuffer是否开启缓存; <strong>useEpollNativeSelector：</strong>是否启用Epoll IO模型。</p>
<h4 id="步骤二"><a href="#步骤二" class="headerlink" title="步骤二"></a>步骤二</h4><p>根据启动属性创建NamesrvController实例，并初始化该实例。NameServerController实例为NameServer核心控制器</p>
<p><em><strong>代码：NamesrvController#initialize</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">initialize</span><span class="params">()</span> &#123;</span><br><span class="line">	<span class="comment">//加载KV配置</span></span><br><span class="line">    <span class="built_in">this</span>.kvConfigManager.load();</span><br><span class="line">	<span class="comment">//创建NettyServer网络处理对象</span></span><br><span class="line">    <span class="built_in">this</span>.remotingServer = <span class="keyword">new</span> <span class="title class_">NettyRemotingServer</span>(<span class="built_in">this</span>.nettyServerConfig, <span class="built_in">this</span>.brokerHousekeepingService);</span><br><span class="line">	<span class="comment">//开启定时任务:每隔10s扫描一次Broker,移除不活跃的Broker</span></span><br><span class="line">    <span class="built_in">this</span>.remotingExecutor =</span><br><span class="line">        Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(), <span class="keyword">new</span> <span class="title class_">ThreadFactoryImpl</span>(<span class="string">&quot;RemotingExecutorThread_&quot;</span>));</span><br><span class="line">    <span class="built_in">this</span>.registerProcessor();</span><br><span class="line">    <span class="built_in">this</span>.scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            NamesrvController.<span class="built_in">this</span>.routeInfoManager.scanNotActiveBroker();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, <span class="number">5</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br><span class="line">	<span class="comment">//开启定时任务:每隔10min打印一次KV配置</span></span><br><span class="line">	<span class="built_in">this</span>.scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            NamesrvController.<span class="built_in">this</span>.kvConfigManager.printAllPeriodically();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, <span class="number">1</span>, <span class="number">10</span>, TimeUnit.MINUTES);</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="步骤三"><a href="#步骤三" class="headerlink" title="步骤三"></a>步骤三</h4><p>在JVM进程关闭之前，先将线程池关闭，及时释放资源</p>
<p><em><strong>代码：NamesrvStartup#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//注册JVM钩子函数代码</span></span><br><span class="line">Runtime.getRuntime().addShutdownHook(<span class="keyword">new</span> <span class="title class_">ShutdownHookThread</span>(log, <span class="keyword">new</span> <span class="title class_">Callable</span>&lt;Void&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Void <span class="title function_">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">//释放资源</span></span><br><span class="line">        controller.shutdown();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;));</span><br></pre></td></tr></table></figure>



<h3 id="2-2-3-路由管理"><a href="#2-2-3-路由管理" class="headerlink" title="2.2.3 路由管理"></a>2.2.3 路由管理</h3><p>NameServer的主要作用是为消息的生产者和消息消费者提供关于主题Topic的路由信息，那么NameServer需要存储路由的基础信息，还要管理Broker节点，包括路由注册、路由删除等。</p>
<h4 id="2-2-3-1-路由元信息"><a href="#2-2-3-1-路由元信息" class="headerlink" title="2.2.3.1 路由元信息"></a>2.2.3.1 路由元信息</h4><p>代码：RouteInfoManager</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HashMap&lt;String<span class="comment">/* topic */</span>, List&lt;QueueData&gt;&gt; topicQueueTable;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HashMap&lt;String<span class="comment">/* brokerName */</span>, BrokerData&gt; brokerAddrTable;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HashMap&lt;String<span class="comment">/* clusterName */</span>, Set&lt;String<span class="comment">/* brokerName */</span>&gt;&gt; clusterAddrTable;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HashMap&lt;String<span class="comment">/* brokerAddr */</span>, BrokerLiveInfo&gt; brokerLiveTable;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HashMap&lt;String<span class="comment">/* brokerAddr */</span>, List&lt;String&gt;<span class="comment">/* Filter Server */</span>&gt; filterServerTable;</span><br></pre></td></tr></table></figure>



<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E8%B7%AF%E7%94%B1%E5%AE%9E%E4%BD%93%E5%9B%BE.png"
                      alt="img"
                ></p>
<p><strong>topicQueueTable：</strong>Topic消息队列路由信息，消息发送时根据路由表进行负载均衡</p>
<p><strong>brokerAddrTable：</strong>Broker基础信息，包括brokerName、所属集群名称、主备Broker地址</p>
<p><strong>clusterAddrTable：</strong>Broker集群信息，存储集群中所有Broker名称</p>
<p><strong>brokerLiveTable：</strong>Broker状态信息，NameServer每次收到心跳包是会替换该信息</p>
<p><strong>filterServerTable：</strong>Broker上的FilterServer列表，用于类模式消息过滤。</p>
<blockquote>
<p>RocketMQ基于定于发布机制，一个Topic拥有多个消息队列，一个Broker为每一个主题创建4个读队列和4个写队列。多个Broker组成一个集群，集群由相同的多台Broker组成Master-Slave架构，brokerId为0代表Master，大于0为Slave。BrokerLiveInfo中的lastUpdateTimestamp存储上次收到Broker心跳包的时间。</p>
</blockquote>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%AE%9E%E4%BD%93%E6%95%B0%E6%8D%AE%E5%AE%9E%E4%BE%8B.png"
                      alt="img"
                ></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%AE%9E%E4%BD%93%E6%95%B0%E6%8D%AE%E5%AE%9E%E4%BE%8B2.png"
                      alt="img"
                ></p>
<h4 id="2-2-3-2-路由注册"><a href="#2-2-3-2-路由注册" class="headerlink" title="2.2.3.2 路由注册"></a>2.2.3.2 路由注册</h4><h4 id="1）发送心跳包"><a href="#1）发送心跳包" class="headerlink" title="1）发送心跳包"></a>1）发送心跳包</h4><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E8%B7%AF%E7%94%B1%E6%B3%A8%E5%86%8C.png"
                      alt="img"
                ></p>
<p>RocketMQ路由注册是通过Broker与NameServer的心跳功能实现的。Broker启动时向集群中所有的NameServer发送心跳信息，每隔30s向集群中所有NameServer发送心跳包，NameServer收到心跳包时会更新brokerLiveTable缓存中BrokerLiveInfo的lastUpdataTimeStamp信息，然后NameServer每隔10s扫描brokerLiveTable，如果连续120S没有收到心跳包，NameServer将移除Broker的路由信息同时关闭Socket连接。</p>
<p><em><strong>代码：BrokerController#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//注册Broker信息</span></span><br><span class="line"><span class="built_in">this</span>.registerBrokerAll(<span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">true</span>);</span><br><span class="line"><span class="comment">//每隔30s上报Broker信息到NameServer</span></span><br><span class="line"><span class="built_in">this</span>.scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            BrokerController.<span class="built_in">this</span>.registerBrokerAll(<span class="literal">true</span>, <span class="literal">false</span>, brokerConfig.isForceRegister());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;registerBrokerAll Exception&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="number">1000</span> * <span class="number">10</span>, Math.max(<span class="number">10000</span>, Math.min(brokerConfig.getRegisterNameServerPeriod(), <span class="number">60000</span>)), </span><br><span class="line">                                                  TimeUnit.MILLISECONDS);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：BrokerOuterAPI#registerBrokerAll</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//获得nameServer地址信息</span></span><br><span class="line">List&lt;String&gt; nameServerAddressList = <span class="built_in">this</span>.remotingClient.getNameServerAddressList();</span><br><span class="line"><span class="comment">//遍历所有nameserver列表</span></span><br><span class="line"><span class="keyword">if</span> (nameServerAddressList != <span class="literal">null</span> &amp;&amp; nameServerAddressList.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//封装请求头</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">RegisterBrokerRequestHeader</span> <span class="variable">requestHeader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RegisterBrokerRequestHeader</span>();</span><br><span class="line">    requestHeader.setBrokerAddr(brokerAddr);</span><br><span class="line">    requestHeader.setBrokerId(brokerId);</span><br><span class="line">    requestHeader.setBrokerName(brokerName);</span><br><span class="line">    requestHeader.setClusterName(clusterName);</span><br><span class="line">    requestHeader.setHaServerAddr(haServerAddr);</span><br><span class="line">    requestHeader.setCompressed(compressed);</span><br><span class="line">	<span class="comment">//封装请求体</span></span><br><span class="line">    <span class="type">RegisterBrokerBody</span> <span class="variable">requestBody</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RegisterBrokerBody</span>();</span><br><span class="line">    requestBody.setTopicConfigSerializeWrapper(topicConfigWrapper);</span><br><span class="line">    requestBody.setFilterServerList(filterServerList);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">byte</span>[] body = requestBody.encode(compressed);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">bodyCrc32</span> <span class="operator">=</span> UtilAll.crc32(body);</span><br><span class="line">    requestHeader.setBodyCrc32(bodyCrc32);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">CountDownLatch</span> <span class="variable">countDownLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(nameServerAddressList.size());</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">final</span> String namesrvAddr : nameServerAddressList) &#123;</span><br><span class="line">        brokerOuterExecutor.execute(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">//分别向NameServer注册</span></span><br><span class="line">                    <span class="type">RegisterBrokerResult</span> <span class="variable">result</span> <span class="operator">=</span> registerBroker(namesrvAddr,oneway, timeoutMills,requestHeader,body);</span><br><span class="line">                    <span class="keyword">if</span> (result != <span class="literal">null</span>) &#123;</span><br><span class="line">                        registerBrokerResultList.add(result);</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    log.info(<span class="string">&quot;register broker[&#123;&#125;]to name server &#123;&#125; OK&quot;</span>, brokerId, namesrvAddr);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    log.warn(<span class="string">&quot;registerBroker Exception, &#123;&#125;&quot;</span>, namesrvAddr, e);</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    countDownLatch.countDown();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：BrokerOutAPI#registerBroker</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (oneway) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.remotingClient.invokeOneway(namesrvAddr, request, timeoutMills);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemotingTooMuchRequestException e) &#123;</span><br><span class="line">        <span class="comment">// Ignore</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">RemotingCommand</span> <span class="variable">response</span> <span class="operator">=</span> <span class="built_in">this</span>.remotingClient.invokeSync(namesrvAddr, request, timeoutMills);</span><br></pre></td></tr></table></figure>



<h5 id="2）处理心跳包"><a href="#2）处理心跳包" class="headerlink" title="2）处理心跳包"></a>2）处理心跳包</h5><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/NameServer%E5%A4%84%E7%90%86%E8%B7%AF%E7%94%B1%E6%B3%A8%E5%86%8C.png"
                      alt="img"
                ></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor`网路处理类解析请求类型，如果请求类型是为***REGISTER_BROKER***，则将请求转发到`RouteInfoManager#regiesterBroker</span><br></pre></td></tr></table></figure>

<p><em><strong>代码：DefaultRequestProcessor#processRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//判断是注册Broker信息</span></span><br><span class="line"><span class="keyword">case</span> RequestCode.REGISTER_BROKER:</span><br><span class="line">	<span class="type">Version</span> <span class="variable">brokerVersion</span> <span class="operator">=</span> MQVersion.value2Version(request.getVersion());</span><br><span class="line">	<span class="keyword">if</span> (brokerVersion.ordinal() &gt;= MQVersion.Version.V3_0_11.ordinal()) &#123;</span><br><span class="line">	    <span class="keyword">return</span> <span class="built_in">this</span>.registerBrokerWithFilterServer(ctx, request);</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//注册Broker信息</span></span><br><span class="line">	    <span class="keyword">return</span> <span class="built_in">this</span>.registerBroker(ctx, request);</span><br><span class="line">	&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultRequestProcessor#registerBroker</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">RegisterBrokerResult</span> <span class="variable">result</span> <span class="operator">=</span> <span class="built_in">this</span>.namesrvController.getRouteInfoManager().registerBroker(</span><br><span class="line">    requestHeader.getClusterName(),</span><br><span class="line">    requestHeader.getBrokerAddr(),</span><br><span class="line">    requestHeader.getBrokerName(),</span><br><span class="line">    requestHeader.getBrokerId(),</span><br><span class="line">    requestHeader.getHaServerAddr(),</span><br><span class="line">    topicConfigWrapper,</span><br><span class="line">    <span class="literal">null</span>,</span><br><span class="line">    ctx.channel()</span><br><span class="line">);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：RouteInfoManager#registerBroker</strong></em></p>
<p>维护路由信息</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//加锁</span></span><br><span class="line"><span class="built_in">this</span>.lock.writeLock().lockInterruptibly();</span><br><span class="line"><span class="comment">//维护clusterAddrTable</span></span><br><span class="line">Set&lt;String&gt; brokerNames = <span class="built_in">this</span>.clusterAddrTable.get(clusterName);</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> == brokerNames) &#123;</span><br><span class="line">    brokerNames = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;String&gt;();</span><br><span class="line">    <span class="built_in">this</span>.clusterAddrTable.put(clusterName, brokerNames);</span><br><span class="line">&#125;</span><br><span class="line">brokerNames.add(brokerName);</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护brokerAddrTable</span></span><br><span class="line"><span class="type">BrokerData</span> <span class="variable">brokerData</span> <span class="operator">=</span> <span class="built_in">this</span>.brokerAddrTable.get(brokerName);</span><br><span class="line"><span class="comment">//第一次注册,则创建brokerData</span></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> == brokerData) &#123;</span><br><span class="line">    registerFirst = <span class="literal">true</span>;</span><br><span class="line">    brokerData = <span class="keyword">new</span> <span class="title class_">BrokerData</span>(clusterName, brokerName, <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;Long, String&gt;());</span><br><span class="line">    <span class="built_in">this</span>.brokerAddrTable.put(brokerName, brokerData);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//非第一次注册,更新Broker</span></span><br><span class="line">Map&lt;Long, String&gt; brokerAddrsMap = brokerData.getBrokerAddrs();</span><br><span class="line">Iterator&lt;Entry&lt;Long, String&gt;&gt; it = brokerAddrsMap.entrySet().iterator();</span><br><span class="line"><span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">    Entry&lt;Long, String&gt; item = it.next();</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> != brokerAddr &amp;&amp; brokerAddr.equals(item.getValue()) &amp;&amp; brokerId != item.getKey()) &#123;</span><br><span class="line">        it.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">String</span> <span class="variable">oldAddr</span> <span class="operator">=</span> brokerData.getBrokerAddrs().put(brokerId, brokerAddr);</span><br><span class="line">registerFirst = registerFirst || (<span class="literal">null</span> == oldAddr);</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护topicQueueTable</span></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> != topicConfigWrapper &amp;&amp; MixAll.MASTER_ID == brokerId) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.isBrokerTopicConfigChanged(brokerAddr, topicConfigWrapper.getDataVersion()) || </span><br><span class="line">        registerFirst) &#123;</span><br><span class="line">        ConcurrentMap&lt;String, TopicConfig&gt; tcTable = topicConfigWrapper.getTopicConfigTable();</span><br><span class="line">        <span class="keyword">if</span> (tcTable != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (Map.Entry&lt;String, TopicConfig&gt; entry : tcTable.entrySet()) &#123;</span><br><span class="line">                <span class="built_in">this</span>.createAndUpdateQueueData(brokerName, entry.getValue());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：RouteInfoManager#createAndUpdateQueueData</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">createAndUpdateQueueData</span><span class="params">(<span class="keyword">final</span> String brokerName, <span class="keyword">final</span> TopicConfig topicConfig)</span> &#123;</span><br><span class="line">    <span class="comment">//创建QueueData</span></span><br><span class="line">	<span class="type">QueueData</span> <span class="variable">queueData</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">QueueData</span>();</span><br><span class="line">	queueData.setBrokerName(brokerName);</span><br><span class="line">	queueData.setWriteQueueNums(topicConfig.getWriteQueueNums());</span><br><span class="line">	queueData.setReadQueueNums(topicConfig.getReadQueueNums());</span><br><span class="line">	queueData.setPerm(topicConfig.getPerm());</span><br><span class="line">	queueData.setTopicSynFlag(topicConfig.getTopicSysFlag());</span><br><span class="line">	<span class="comment">//获得topicQueueTable中队列集合</span></span><br><span class="line">	List&lt;QueueData&gt; queueDataList = <span class="built_in">this</span>.topicQueueTable.get(topicConfig.getTopicName());</span><br><span class="line">    <span class="comment">//topicQueueTable为空,则直接添加queueData到队列集合</span></span><br><span class="line">	<span class="keyword">if</span> (<span class="literal">null</span> == queueDataList) &#123;</span><br><span class="line">	    queueDataList = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;QueueData&gt;();</span><br><span class="line">	    queueDataList.add(queueData);</span><br><span class="line">	    <span class="built_in">this</span>.topicQueueTable.put(topicConfig.getTopicName(), queueDataList);</span><br><span class="line">	    log.info(<span class="string">&quot;new topic registered, &#123;&#125; &#123;&#125;&quot;</span>, topicConfig.getTopicName(), queueData);</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//判断是否是新的队列</span></span><br><span class="line">	    <span class="type">boolean</span> <span class="variable">addNewOne</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">	    Iterator&lt;QueueData&gt; it = queueDataList.iterator();</span><br><span class="line">	    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">	        <span class="type">QueueData</span> <span class="variable">qd</span> <span class="operator">=</span> it.next();</span><br><span class="line">            <span class="comment">//如果brokerName相同,代表不是新的队列</span></span><br><span class="line">	        <span class="keyword">if</span> (qd.getBrokerName().equals(brokerName)) &#123;</span><br><span class="line">	            <span class="keyword">if</span> (qd.equals(queueData)) &#123;</span><br><span class="line">	                addNewOne = <span class="literal">false</span>;</span><br><span class="line">	        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">	                    log.info(<span class="string">&quot;topic changed, &#123;&#125; OLD: &#123;&#125; NEW: &#123;&#125;&quot;</span>, topicConfig.getTopicName(), qd,</span><br><span class="line">	                        queueData);</span><br><span class="line">	                    it.remove();</span><br><span class="line">	                &#125;</span><br><span class="line">	            &#125;</span><br><span class="line">	        &#125;</span><br><span class="line">		<span class="comment">//如果是新的队列,则添加队列到queueDataList</span></span><br><span class="line">        <span class="keyword">if</span> (addNewOne) &#123;</span><br><span class="line">            queueDataList.add(queueData);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护brokerLiveTable</span></span><br><span class="line"><span class="type">BrokerLiveInfo</span> <span class="variable">prevBrokerLiveInfo</span> <span class="operator">=</span> <span class="built_in">this</span>.brokerLiveTable.put(brokerAddr,<span class="keyword">new</span> <span class="title class_">BrokerLiveInfo</span>(</span><br><span class="line">    System.currentTimeMillis(),</span><br><span class="line">    topicConfigWrapper.getDataVersion(),</span><br><span class="line">    channel,</span><br><span class="line">    haServerAddr));</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护filterServerList</span></span><br><span class="line"><span class="keyword">if</span> (filterServerList != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (filterServerList.isEmpty()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.filterServerTable.remove(brokerAddr);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.filterServerTable.put(brokerAddr, filterServerList);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (MixAll.MASTER_ID != brokerId) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">masterAddr</span> <span class="operator">=</span> brokerData.getBrokerAddrs().get(MixAll.MASTER_ID);</span><br><span class="line">    <span class="keyword">if</span> (masterAddr != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">BrokerLiveInfo</span> <span class="variable">brokerLiveInfo</span> <span class="operator">=</span> <span class="built_in">this</span>.brokerLiveTable.get(masterAddr);</span><br><span class="line">        <span class="keyword">if</span> (brokerLiveInfo != <span class="literal">null</span>) &#123;</span><br><span class="line">            result.setHaServerAddr(brokerLiveInfo.getHaServerAddr());</span><br><span class="line">            result.setMasterAddr(masterAddr);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="2-2-3-3-路由删除"><a href="#2-2-3-3-路由删除" class="headerlink" title="2.2.3.3 路由删除"></a>2.2.3.3 路由删除</h4><p>Broker每隔30s向NameServer发送一个心跳包，心跳包包含BrokerId，Broker地址，Broker名称，Broker所属集群名称、Broker关联的FilterServer列表。但是如果Broker宕机，NameServer无法收到心跳包，此时NameServer如何来剔除这些失效的Broker呢？NameServer会每隔10s扫描brokerLiveTable状态表，如果BrokerLive的lastUpdateTimestamp的时间戳距当前时间超过120s，则认为Broker失效，移除该Broker，关闭与Broker连接，同时更新topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable。</p>
<p><strong>RocketMQ有两个触发点来删除路由信息</strong>：</p>
<ul>
<li>NameServer定期扫描brokerLiveTable检测上次心跳包与当前系统的时间差，如果时间超过120s，则需要移除broker。</li>
<li>Broker在正常关闭的情况下，会执行unregisterBroker指令</li>
</ul>
<p>这两种方式路由删除的方法都是一样的，就是从相关路由表中删除与该broker相关的信息。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E8%B7%AF%E7%94%B1%E5%88%A0%E9%99%A4.png"
                      alt="img"
                ></p>
<p><em><strong>代码：NamesrvController#initialize</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//每隔10s扫描一次为活跃Broker</span></span><br><span class="line"><span class="built_in">this</span>.scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        NamesrvController.<span class="built_in">this</span>.routeInfoManager.scanNotActiveBroker();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="number">5</span>, <span class="number">10</span>, TimeUnit.SECONDS);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：RouteInfoManager#scanNotActiveBroker</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scanNotActiveBroker</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//获得brokerLiveTable</span></span><br><span class="line">    Iterator&lt;Entry&lt;String, BrokerLiveInfo&gt;&gt; it = <span class="built_in">this</span>.brokerLiveTable.entrySet().iterator();</span><br><span class="line">    <span class="comment">//遍历brokerLiveTable</span></span><br><span class="line">    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">        Entry&lt;String, BrokerLiveInfo&gt; next = it.next();</span><br><span class="line">        <span class="type">long</span> <span class="variable">last</span> <span class="operator">=</span> next.getValue().getLastUpdateTimestamp();</span><br><span class="line">        <span class="comment">//如果收到心跳包的时间距当时时间是否超过120s</span></span><br><span class="line">        <span class="keyword">if</span> ((last + BROKER_CHANNEL_EXPIRED_TIME) &lt; System.currentTimeMillis()) &#123;</span><br><span class="line">            <span class="comment">//关闭连接</span></span><br><span class="line">            RemotingUtil.closeChannel(next.getValue().getChannel());</span><br><span class="line">            <span class="comment">//移除broker</span></span><br><span class="line">            it.remove();</span><br><span class="line">            <span class="comment">//维护路由表</span></span><br><span class="line">            <span class="built_in">this</span>.onChannelDestroy(next.getKey(), next.getValue().getChannel());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：RouteInfoManager#onChannelDestroy</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//申请写锁,根据brokerAddress从brokerLiveTable和filterServerTable移除</span></span><br><span class="line"><span class="built_in">this</span>.lock.writeLock().lockInterruptibly();</span><br><span class="line"><span class="built_in">this</span>.brokerLiveTable.remove(brokerAddrFound);</span><br><span class="line"><span class="built_in">this</span>.filterServerTable.remove(brokerAddrFound);</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护brokerAddrTable</span></span><br><span class="line"><span class="type">String</span> <span class="variable">brokerNameFound</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="type">boolean</span> <span class="variable">removeBrokerName</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">Iterator&lt;Entry&lt;String, BrokerData&gt;&gt; itBrokerAddrTable =<span class="built_in">this</span>.brokerAddrTable.entrySet().iterator();</span><br><span class="line"><span class="comment">//遍历brokerAddrTable</span></span><br><span class="line"><span class="keyword">while</span> (itBrokerAddrTable.hasNext() &amp;&amp; (<span class="literal">null</span> == brokerNameFound)) &#123;</span><br><span class="line">    <span class="type">BrokerData</span> <span class="variable">brokerData</span> <span class="operator">=</span> itBrokerAddrTable.next().getValue();</span><br><span class="line">    <span class="comment">//遍历broker地址</span></span><br><span class="line">    Iterator&lt;Entry&lt;Long, String&gt;&gt; it = brokerData.getBrokerAddrs().entrySet().iterator();</span><br><span class="line">    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">        Entry&lt;Long, String&gt; entry = it.next();</span><br><span class="line">        <span class="type">Long</span> <span class="variable">brokerId</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="type">String</span> <span class="variable">brokerAddr</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">        <span class="comment">//根据broker地址移除brokerAddr</span></span><br><span class="line">        <span class="keyword">if</span> (brokerAddr.equals(brokerAddrFound)) &#123;</span><br><span class="line">            brokerNameFound = brokerData.getBrokerName();</span><br><span class="line">            it.remove();</span><br><span class="line">            log.info(<span class="string">&quot;remove brokerAddr[&#123;&#125;, &#123;&#125;] from brokerAddrTable, because channel destroyed&quot;</span>,</span><br><span class="line">                brokerId, brokerAddr);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//如果当前主题只包含待移除的broker,则移除该topic</span></span><br><span class="line">    <span class="keyword">if</span> (brokerData.getBrokerAddrs().isEmpty()) &#123;</span><br><span class="line">        removeBrokerName = <span class="literal">true</span>;</span><br><span class="line">        itBrokerAddrTable.remove();</span><br><span class="line">        log.info(<span class="string">&quot;remove brokerName[&#123;&#125;] from brokerAddrTable, because channel destroyed&quot;</span>,</span><br><span class="line">            brokerData.getBrokerName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护clusterAddrTable</span></span><br><span class="line"><span class="keyword">if</span> (brokerNameFound != <span class="literal">null</span> &amp;&amp; removeBrokerName) &#123;</span><br><span class="line">    Iterator&lt;Entry&lt;String, Set&lt;String&gt;&gt;&gt; it = <span class="built_in">this</span>.clusterAddrTable.entrySet().iterator();</span><br><span class="line">    <span class="comment">//遍历clusterAddrTable</span></span><br><span class="line">    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">        Entry&lt;String, Set&lt;String&gt;&gt; entry = it.next();</span><br><span class="line">        <span class="comment">//获得集群名称</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">clusterName</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="comment">//获得集群中brokerName集合</span></span><br><span class="line">        Set&lt;String&gt; brokerNames = entry.getValue();</span><br><span class="line">        <span class="comment">//从brokerNames中移除brokerNameFound</span></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">removed</span> <span class="operator">=</span> brokerNames.remove(brokerNameFound);</span><br><span class="line">        <span class="keyword">if</span> (removed) &#123;</span><br><span class="line">            log.info(<span class="string">&quot;remove brokerName[&#123;&#125;], clusterName[&#123;&#125;] from clusterAddrTable, because channel destroyed&quot;</span>,</span><br><span class="line">                brokerNameFound, clusterName);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (brokerNames.isEmpty()) &#123;</span><br><span class="line">                log.info(<span class="string">&quot;remove the clusterName[&#123;&#125;] from clusterAddrTable, because channel destroyed and no broker in this cluster&quot;</span>,</span><br><span class="line">                    clusterName);</span><br><span class="line">                <span class="comment">//如果集群中不包含任何broker,则移除该集群</span></span><br><span class="line">                it.remove();</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//维护topicQueueTable队列</span></span><br><span class="line"><span class="keyword">if</span> (removeBrokerName) &#123;</span><br><span class="line">    <span class="comment">//遍历topicQueueTable</span></span><br><span class="line">    Iterator&lt;Entry&lt;String, List&lt;QueueData&gt;&gt;&gt; itTopicQueueTable =</span><br><span class="line">        <span class="built_in">this</span>.topicQueueTable.entrySet().iterator();</span><br><span class="line">    <span class="keyword">while</span> (itTopicQueueTable.hasNext()) &#123;</span><br><span class="line">        Entry&lt;String, List&lt;QueueData&gt;&gt; entry = itTopicQueueTable.next();</span><br><span class="line">        <span class="comment">//主题名称</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">        <span class="comment">//队列集合</span></span><br><span class="line">        List&lt;QueueData&gt; queueDataList = entry.getValue();</span><br><span class="line">		<span class="comment">//遍历该主题队列</span></span><br><span class="line">        Iterator&lt;QueueData&gt; itQueueData = queueDataList.iterator();</span><br><span class="line">        <span class="keyword">while</span> (itQueueData.hasNext()) &#123;</span><br><span class="line">            <span class="comment">//从队列中移除为活跃broker信息</span></span><br><span class="line">            <span class="type">QueueData</span> <span class="variable">queueData</span> <span class="operator">=</span> itQueueData.next();</span><br><span class="line">            <span class="keyword">if</span> (queueData.getBrokerName().equals(brokerNameFound)) &#123;</span><br><span class="line">                itQueueData.remove();</span><br><span class="line">                log.info(<span class="string">&quot;remove topic[&#123;&#125; &#123;&#125;], from topicQueueTable, because channel destroyed&quot;</span>,</span><br><span class="line">                    topic, queueData);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">		<span class="comment">//如果该topic的队列为空,则移除该topic</span></span><br><span class="line">        <span class="keyword">if</span> (queueDataList.isEmpty()) &#123;</span><br><span class="line">            itTopicQueueTable.remove();</span><br><span class="line">            log.info(<span class="string">&quot;remove topic[&#123;&#125;] all queue, from topicQueueTable, because channel destroyed&quot;</span>,</span><br><span class="line">                topic);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//释放写锁</span></span><br><span class="line"><span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.lock.writeLock().unlock();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="2-2-3-4-路由发现"><a href="#2-2-3-4-路由发现" class="headerlink" title="2.2.3.4 路由发现"></a>2.2.3.4 路由发现</h4><p>RocketMQ路由发现是非实时的，当Topic路由出现变化后，NameServer不会主动推送给客户端，而是由客户端定时拉取主题最新的路由。</p>
<p><em><strong>代码：DefaultRequestProcessor#getRouteInfoByTopic</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> RemotingCommand <span class="title function_">getRouteInfoByTopic</span><span class="params">(ChannelHandlerContext ctx,</span></span><br><span class="line"><span class="params">    RemotingCommand request)</span> <span class="keyword">throws</span> RemotingCommandException &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">RemotingCommand</span> <span class="variable">response</span> <span class="operator">=</span> RemotingCommand.createResponseCommand(<span class="literal">null</span>);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">GetRouteInfoRequestHeader</span> <span class="variable">requestHeader</span> <span class="operator">=</span></span><br><span class="line">        (GetRouteInfoRequestHeader) request.decodeCommandCustomHeader(GetRouteInfoRequestHeader.class);</span><br><span class="line">	<span class="comment">//调用RouteInfoManager的方法,从路由表topicQueueTable、brokerAddrTable、filterServerTable中分别填充TopicRouteData的List&lt;QueueData&gt;、List&lt;BrokerData&gt;、filterServer</span></span><br><span class="line">    <span class="type">TopicRouteData</span> <span class="variable">topicRouteData</span> <span class="operator">=</span> <span class="built_in">this</span>.namesrvController.getRouteInfoManager().pickupTopicRouteData(requestHeader.getTopic());</span><br><span class="line">	<span class="comment">//如果找到主题对应你的路由信息并且该主题为顺序消息，则从NameServer KVConfig中获取关于顺序消息相关的配置填充路由信息</span></span><br><span class="line">    <span class="keyword">if</span> (topicRouteData != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.namesrvController.getNamesrvConfig().isOrderMessageEnable()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">orderTopicConf</span> <span class="operator">=</span></span><br><span class="line">                <span class="built_in">this</span>.namesrvController.getKvConfigManager().getKVConfig(NamesrvUtil.NAMESPACE_ORDER_TOPIC_CONFIG,</span><br><span class="line">                    requestHeader.getTopic());</span><br><span class="line">            topicRouteData.setOrderTopicConf(orderTopicConf);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">byte</span>[] content = topicRouteData.encode();</span><br><span class="line">        response.setBody(content);</span><br><span class="line">        response.setCode(ResponseCode.SUCCESS);</span><br><span class="line">        response.setRemark(<span class="literal">null</span>);</span><br><span class="line">        <span class="keyword">return</span> response;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    response.setCode(ResponseCode.TOPIC_NOT_EXIST);</span><br><span class="line">    response.setRemark(<span class="string">&quot;No topic route info in name server for the topic: &quot;</span> + requestHeader.getTopic()</span><br><span class="line">        + FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL));</span><br><span class="line">    <span class="keyword">return</span> response;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-2-4-小结"><a href="#2-2-4-小结" class="headerlink" title="2.2.4 小结"></a>2.2.4 小结</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/NameServer%E5%B0%8F%E7%BB%93.png"
                      alt="img"
                ></p>
<h2 id="2-3-Producer"><a href="#2-3-Producer" class="headerlink" title="2.3 Producer"></a>2.3 Producer</h2><p>消息生产者的代码都在client模块中，相对于RocketMQ来讲，消息生产者就是客户端，也是消息的提供者。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/DefaultMQProducer%E7%B1%BB%E5%9B%BE.png"
                      alt="img"
                ></p>
<p>###2.3.1 方法和属性</p>
<p>####1）主要方法介绍</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/MQAdmin.png"
                      alt="img"
                ></p>
<ul>
<li><pre><code class="java">//创建主题
void createTopic(final String key, final String newTopic, final int queueNum) throws MQClientException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //根据时间戳从队列中查找消息偏移量</span><br><span class="line">  long searchOffset(final MessageQueue mq, final long timestamp)</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//查找消息队列中最大的偏移量
long maxOffset(final MessageQueue mq) throws MQClientException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //查找消息队列中最小的偏移量</span><br><span class="line">  long minOffset(final MessageQueue mq) </span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//根据偏移量查找消息
MessageExt viewMessage(final String offsetMsgId) throws RemotingException, MQBrokerException,
        InterruptedException, MQClientException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //根据条件查找消息</span><br><span class="line">  QueryResult queryMessage(final String topic, final String key, final int maxNum, final long begin,</span><br><span class="line">          final long end) throws MQClientException, InterruptedException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//根据消息ID和主题查找消息
MessageExt viewMessage(String topic,String msgId) throws RemotingException, MQBrokerException, InterruptedException, MQClientException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">![img](https://pchaoo.gitee.io/blog/img/rocketmq/MQProducer.png)</span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //启动</span><br><span class="line">  void start() throws MQClientException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//关闭
void shutdown();
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //查找该主题下所有消息</span><br><span class="line">  List&lt;MessageQueue&gt; fetchPublishMessageQueues(final String topic) throws MQClientException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//同步发送消息
SendResult send(final Message msg) throws MQClientException, RemotingException, MQBrokerException,
        InterruptedException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //同步超时发送消息</span><br><span class="line">  SendResult send(final Message msg, final long timeout) throws MQClientException,</span><br><span class="line">          RemotingException, MQBrokerException, InterruptedException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//异步发送消息
void send(final Message msg, final SendCallback sendCallback) throws MQClientException,
        RemotingException, InterruptedException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //异步超时发送消息</span><br><span class="line">  void send(final Message msg, final SendCallback sendCallback, final long timeout)</span><br><span class="line">      throws MQClientException, RemotingException, InterruptedException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//发送单向消息
void sendOneway(final Message msg) throws MQClientException, RemotingException,
    InterruptedException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //选择指定队列同步发送消息</span><br><span class="line">  SendResult send(final Message msg, final MessageQueue mq) throws MQClientException,</span><br><span class="line">      RemotingException, MQBrokerException, InterruptedException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><pre><code class="java">//选择指定队列异步发送消息
void send(final Message msg, final MessageQueue mq, final SendCallback sendCallback)
    throws MQClientException, RemotingException, InterruptedException;
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">- ```java</span><br><span class="line">  //选择指定队列单项发送消息</span><br><span class="line">  void sendOneway(final Message msg, final MessageQueue mq) throws MQClientException,</span><br><span class="line">      RemotingException, InterruptedException;</span><br></pre></td></tr></table></figure>

</code></pre>
</li>
<li><p>&#96;&#96;&#96;java<br>&#x2F;&#x2F;批量发送消息<br>SendResult send(final Collection<Message> msgs) throws MQClientException, RemotingException, MQBrokerException,InterruptedException;</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  </span><br><span class="line"></span><br><span class="line">\####2）属性介绍</span><br><span class="line"></span><br><span class="line">![img](https://pchaoo.gitee.io/blog/img/rocketmq/DefaultMQProducer%E5%B1%9E%E6%80%A7.png)</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">producerGroup：生产者所属组</span><br><span class="line">createTopicKey：默认Topic</span><br><span class="line">defaultTopicQueueNums：默认主题在每一个Broker队列数量</span><br><span class="line">sendMsgTimeout：发送消息默认超时时间，默认3s</span><br><span class="line">compressMsgBodyOverHowmuch：消息体超过该值则启用压缩，默认4k</span><br><span class="line">retryTimesWhenSendFailed：同步方式发送消息重试次数，默认为2，总共执行3次</span><br><span class="line">retryTimesWhenSendAsyncFailed：异步方法发送消息重试次数，默认为2</span><br><span class="line">retryAnotherBrokerWhenNotStoreOK：消息重试时选择另外一个Broker时，是否不等待存储结果就返回，默认为false</span><br><span class="line">maxMessageSize：允许发送的最大消息长度，默认为4M</span><br></pre></td></tr></table></figure></li>
</ul>
<h3 id="2-3-2-启动流程"><a href="#2-3-2-启动流程" class="headerlink" title="2.3.2 启动流程"></a>2.3.2 启动流程</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E7%94%9F%E4%BA%A7%E8%80%85%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p><em><strong>代码：DefaultMQProducerImpl#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//检查生产者组是否满足要求</span></span><br><span class="line"><span class="built_in">this</span>.checkConfig();</span><br><span class="line"><span class="comment">//更改当前instanceName为进程ID</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">this</span>.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) &#123;</span><br><span class="line">    <span class="built_in">this</span>.defaultMQProducer.changeInstanceNameToPID();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//获得MQ客户端实例</span></span><br><span class="line"><span class="built_in">this</span>.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(<span class="built_in">this</span>.defaultMQProducer, rpcHook);</span><br></pre></td></tr></table></figure>



<blockquote>
<p>整个JVM中只存在一个MQClientManager实例，维护一个MQClientInstance缓存表</p>
<p>ConcurrentMap&lt;String&#x2F;* clientId *&#x2F;, MQClientInstance&gt; factoryTable &#x3D; new ConcurrentHashMap&lt;String,MQClientInstance&gt;();</p>
<p>同一个clientId只会创建一个MQClientInstance。</p>
<p>MQClientInstance封装了RocketMQ网络处理API，是消息生产者和消息消费者与NameServer、Broker打交道的网络通道</p>
</blockquote>
<p><em><strong>代码：MQClientManager#getAndCreateMQClientInstance</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> MQClientInstance <span class="title function_">getAndCreateMQClientInstance</span><span class="params">(<span class="keyword">final</span> ClientConfig clientConfig, </span></span><br><span class="line"><span class="params">                                                     RPCHook rpcHook)</span> &#123;</span><br><span class="line">    <span class="comment">//构建客户端ID</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">clientId</span> <span class="operator">=</span> clientConfig.buildMQClientId();</span><br><span class="line">    <span class="comment">//根据客户端ID或者客户端实例</span></span><br><span class="line">    <span class="type">MQClientInstance</span> <span class="variable">instance</span> <span class="operator">=</span> <span class="built_in">this</span>.factoryTable.get(clientId);</span><br><span class="line">    <span class="comment">//实例如果为空就创建新的实例,并添加到实例表中</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == instance) &#123;</span><br><span class="line">        instance =</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">MQClientInstance</span>(clientConfig.cloneClientConfig(),</span><br><span class="line">                <span class="built_in">this</span>.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);</span><br><span class="line">        <span class="type">MQClientInstance</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="built_in">this</span>.factoryTable.putIfAbsent(clientId, instance);</span><br><span class="line">        <span class="keyword">if</span> (prev != <span class="literal">null</span>) &#123;</span><br><span class="line">            instance = prev;</span><br><span class="line">            log.warn(<span class="string">&quot;Returned Previous MQClientInstance for clientId:[&#123;&#125;]&quot;</span>, clientId);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            log.info(<span class="string">&quot;Created new MQClientInstance for clientId:[&#123;&#125;]&quot;</span>, clientId);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> instance;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMQProducerImpl#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//注册当前生产者到到MQClientInstance管理中,方便后续调用网路请求</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">registerOK</span> <span class="operator">=</span> mQClientFactory.registerProducer(<span class="built_in">this</span>.defaultMQProducer.getProducerGroup(), <span class="built_in">this</span>);</span><br><span class="line"><span class="keyword">if</span> (!registerOK) &#123;</span><br><span class="line">    <span class="built_in">this</span>.serviceState = ServiceState.CREATE_JUST;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(<span class="string">&quot;The producer group[&quot;</span> + <span class="built_in">this</span>.defaultMQProducer.getProducerGroup()</span><br><span class="line">        + <span class="string">&quot;] has been created before, specify another name please.&quot;</span> + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),</span><br><span class="line">        <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//启动生产者</span></span><br><span class="line"><span class="keyword">if</span> (startFactory) &#123;</span><br><span class="line">    mQClientFactory.start();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-3-3-消息发送"><a href="#2-3-3-消息发送" class="headerlink" title="2.3.3 消息发送"></a>2.3.3 消息发送</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81.png"
                      alt="img"
                ></p>
<p><em><strong>代码：DefaultMQProducerImpl#send(Message msg)</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//发送消息</span></span><br><span class="line"><span class="keyword">public</span> SendResult <span class="title function_">send</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> send(msg, <span class="built_in">this</span>.defaultMQProducer.getSendMsgTimeout());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMQProducerImpl#send(Message msg,long timeout)</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//发送消息,默认超时时间为3s</span></span><br><span class="line"><span class="keyword">public</span> SendResult <span class="title function_">send</span><span class="params">(Message msg,<span class="type">long</span> timeout)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.sendDefaultImpl(msg, CommunicationMode.SYNC, <span class="literal">null</span>, timeout);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMQProducerImpl#sendDefaultImpl</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//校验消息</span></span><br><span class="line">Validators.checkMessage(msg, <span class="built_in">this</span>.defaultMQProducer);</span><br></pre></td></tr></table></figure>



<p>####1）验证消息</p>
<p><em><strong>代码：Validators#checkMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">checkMessage</span><span class="params">(Message msg, DefaultMQProducer defaultMQProducer)</span></span><br><span class="line">    <span class="keyword">throws</span> MQClientException &#123;</span><br><span class="line">    <span class="comment">//判断是否为空</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == msg) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(ResponseCode.MESSAGE_ILLEGAL, <span class="string">&quot;the message is null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 校验主题</span></span><br><span class="line">    Validators.checkTopic(msg.getTopic());</span><br><span class="line">		</span><br><span class="line">    <span class="comment">// 校验消息体</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == msg.getBody()) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(ResponseCode.MESSAGE_ILLEGAL, <span class="string">&quot;the message body is null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="number">0</span> == msg.getBody().length) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(ResponseCode.MESSAGE_ILLEGAL, <span class="string">&quot;the message body length is zero&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (msg.getBody().length &gt; defaultMQProducer.getMaxMessageSize()) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(ResponseCode.MESSAGE_ILLEGAL,</span><br><span class="line">            <span class="string">&quot;the message body size over max value, MAX: &quot;</span> + defaultMQProducer.getMaxMessageSize());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>####2）查找路由</p>
<p><em><strong>代码：DefaultMQProducerImpl#tryToFindTopicPublishInfo</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> TopicPublishInfo <span class="title function_">tryToFindTopicPublishInfo</span><span class="params">(<span class="keyword">final</span> String topic)</span> &#123;</span><br><span class="line">    <span class="comment">//从缓存中获得主题的路由信息</span></span><br><span class="line">    <span class="type">TopicPublishInfo</span> <span class="variable">topicPublishInfo</span> <span class="operator">=</span> <span class="built_in">this</span>.topicPublishInfoTable.get(topic);</span><br><span class="line">    <span class="comment">//路由信息为空,则从NameServer获取路由</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == topicPublishInfo || !topicPublishInfo.ok()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.topicPublishInfoTable.putIfAbsent(topic, <span class="keyword">new</span> <span class="title class_">TopicPublishInfo</span>());</span><br><span class="line">        <span class="built_in">this</span>.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);</span><br><span class="line">        topicPublishInfo = <span class="built_in">this</span>.topicPublishInfoTable.get(topic);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) &#123;</span><br><span class="line">        <span class="keyword">return</span> topicPublishInfo;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//如果未找到当前主题的路由信息,则用默认主题继续查找</span></span><br><span class="line">        <span class="built_in">this</span>.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, <span class="literal">true</span>, <span class="built_in">this</span>.defaultMQProducer);</span><br><span class="line">        topicPublishInfo = <span class="built_in">this</span>.topicPublishInfoTable.get(topic);</span><br><span class="line">        <span class="keyword">return</span> topicPublishInfo;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/Topic%E8%B7%AF%E7%94%B1%E4%BF%A1%E6%81%AF.png"
                      alt="img"
                ></p>
<p><em><strong>代码：TopicPublishInfo</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TopicPublishInfo</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">orderTopic</span> <span class="operator">=</span> <span class="literal">false</span>;	<span class="comment">//是否是顺序消息</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">haveTopicRouterInfo</span> <span class="operator">=</span> <span class="literal">false</span>; </span><br><span class="line">    <span class="keyword">private</span> List&lt;MessageQueue&gt; messageQueueList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;MessageQueue&gt;();	<span class="comment">//该主题消息队列</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">ThreadLocalIndex</span> <span class="variable">sendWhichQueue</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadLocalIndex</span>();<span class="comment">//每选择一次消息队列,该值+1</span></span><br><span class="line">    <span class="keyword">private</span> TopicRouteData topicRouteData;<span class="comment">//关联Topic路由元信息</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQClientInstance#updateTopicRouteInfoFromNameServer</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">TopicRouteData topicRouteData;</span><br><span class="line"><span class="comment">//使用默认主题从NameServer获取路由信息</span></span><br><span class="line"><span class="keyword">if</span> (isDefault &amp;&amp; defaultMQProducer != <span class="literal">null</span>) &#123;</span><br><span class="line">    topicRouteData = <span class="built_in">this</span>.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(),</span><br><span class="line">        <span class="number">1000</span> * <span class="number">3</span>);</span><br><span class="line">    <span class="keyword">if</span> (topicRouteData != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (QueueData data : topicRouteData.getQueueDatas()) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">queueNums</span> <span class="operator">=</span> Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums());</span><br><span class="line">            data.setReadQueueNums(queueNums);</span><br><span class="line">            data.setWriteQueueNums(queueNums);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">//使用指定主题从NameServer获取路由信息</span></span><br><span class="line">    topicRouteData = <span class="built_in">this</span>.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, <span class="number">1000</span> * <span class="number">3</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQClientInstance#updateTopicRouteInfoFromNameServer</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//判断路由是否需要更改</span></span><br><span class="line"><span class="type">TopicRouteData</span> <span class="variable">old</span> <span class="operator">=</span> <span class="built_in">this</span>.topicRouteTable.get(topic);</span><br><span class="line"><span class="type">boolean</span> <span class="variable">changed</span> <span class="operator">=</span> topicRouteDataIsChange(old, topicRouteData);</span><br><span class="line"><span class="keyword">if</span> (!changed) &#123;</span><br><span class="line">    changed = <span class="built_in">this</span>.isNeedUpdateTopicRouteInfo(topic);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    log.info(<span class="string">&quot;the topic[&#123;&#125;] route info changed, old[&#123;&#125;] ,new[&#123;&#125;]&quot;</span>, topic, old, topicRouteData);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQClientInstance#updateTopicRouteInfoFromNameServer</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (changed) &#123;</span><br><span class="line">    <span class="comment">//将topicRouteData转换为发布队列</span></span><br><span class="line">    <span class="type">TopicPublishInfo</span> <span class="variable">publishInfo</span> <span class="operator">=</span> topicRouteData2TopicPublishInfo(topic, topicRouteData);</span><br><span class="line">    publishInfo.setHaveTopicRouterInfo(<span class="literal">true</span>);</span><br><span class="line">    <span class="comment">//遍历生产</span></span><br><span class="line">    Iterator&lt;Entry&lt;String, MQProducerInner&gt;&gt; it = <span class="built_in">this</span>.producerTable.entrySet().iterator();</span><br><span class="line">    <span class="keyword">while</span> (it.hasNext()) &#123;</span><br><span class="line">        Entry&lt;String, MQProducerInner&gt; entry = it.next();</span><br><span class="line">        <span class="type">MQProducerInner</span> <span class="variable">impl</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">        <span class="keyword">if</span> (impl != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//生产者不为空时,更新publishInfo信息</span></span><br><span class="line">            impl.updateTopicPublishInfo(topic, publishInfo);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQClientInstance#topicRouteData2TopicPublishInfo</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> TopicPublishInfo <span class="title function_">topicRouteData2TopicPublishInfo</span><span class="params">(<span class="keyword">final</span> String topic, <span class="keyword">final</span> TopicRouteData route)</span> &#123;</span><br><span class="line">    	<span class="comment">//创建TopicPublishInfo对象</span></span><br><span class="line">        <span class="type">TopicPublishInfo</span> <span class="variable">info</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">TopicPublishInfo</span>();</span><br><span class="line">    	<span class="comment">//关联topicRoute</span></span><br><span class="line">        info.setTopicRouteData(route);</span><br><span class="line">    	<span class="comment">//顺序消息,更新TopicPublishInfo</span></span><br><span class="line">        <span class="keyword">if</span> (route.getOrderTopicConf() != <span class="literal">null</span> &amp;&amp; route.getOrderTopicConf().length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            String[] brokers = route.getOrderTopicConf().split(<span class="string">&quot;;&quot;</span>);</span><br><span class="line">            <span class="keyword">for</span> (String broker : brokers) &#123;</span><br><span class="line">                String[] item = broker.split(<span class="string">&quot;:&quot;</span>);</span><br><span class="line">                <span class="type">int</span> <span class="variable">nums</span> <span class="operator">=</span> Integer.parseInt(item[<span class="number">1</span>]);</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; nums; i++) &#123;</span><br><span class="line">                    <span class="type">MessageQueue</span> <span class="variable">mq</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageQueue</span>(topic, item[<span class="number">0</span>], i);</span><br><span class="line">                    info.getMessageQueueList().add(mq);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            info.setOrderTopic(<span class="literal">true</span>);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">//非顺序消息更新TopicPublishInfo</span></span><br><span class="line">            List&lt;QueueData&gt; qds = route.getQueueDatas();</span><br><span class="line">            Collections.sort(qds);</span><br><span class="line">            <span class="comment">//遍历topic队列信息</span></span><br><span class="line">            <span class="keyword">for</span> (QueueData qd : qds) &#123;</span><br><span class="line">                <span class="comment">//是否是写队列</span></span><br><span class="line">                <span class="keyword">if</span> (PermName.isWriteable(qd.getPerm())) &#123;</span><br><span class="line">                    <span class="type">BrokerData</span> <span class="variable">brokerData</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">                    <span class="comment">//遍历写队列Broker</span></span><br><span class="line">                    <span class="keyword">for</span> (BrokerData bd : route.getBrokerDatas()) &#123;</span><br><span class="line">                        <span class="comment">//根据名称获得读队列对应的Broker</span></span><br><span class="line">                        <span class="keyword">if</span> (bd.getBrokerName().equals(qd.getBrokerName())) &#123;</span><br><span class="line">                        brokerData = bd;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (<span class="literal">null</span> == brokerData) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!brokerData.getBrokerAddrs().containsKey(MixAll.MASTER_ID)) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">				<span class="comment">//封装TopicPublishInfo写队列</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; qd.getWriteQueueNums(); i++) &#123;</span><br><span class="line">                    <span class="type">MessageQueue</span> <span class="variable">mq</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MessageQueue</span>(topic, qd.getBrokerName(), i);</span><br><span class="line">                    info.getMessageQueueList().add(mq);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        info.setOrderTopic(<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//返回TopicPublishInfo对象</span></span><br><span class="line">    <span class="keyword">return</span> info;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="3）选择队列"><a href="#3）选择队列" class="headerlink" title="3）选择队列"></a>3）选择队列</h4><ul>
<li>默认不启用Broker故障延迟机制</li>
</ul>
<p><em><strong>代码：TopicPublishInfo#selectOneMessageQueue(lastBrokerName)</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> MessageQueue <span class="title function_">selectOneMessageQueue</span><span class="params">(<span class="keyword">final</span> String lastBrokerName)</span> &#123;</span><br><span class="line">    <span class="comment">//第一次选择队列</span></span><br><span class="line">    <span class="keyword">if</span> (lastBrokerName == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> selectOneMessageQueue();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//sendWhichQueue</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="built_in">this</span>.sendWhichQueue.getAndIncrement();</span><br><span class="line">        <span class="comment">//遍历消息队列集合</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="built_in">this</span>.messageQueueList.size(); i++) &#123;</span><br><span class="line">            <span class="comment">//sendWhichQueue自增后取模</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">pos</span> <span class="operator">=</span> Math.abs(index++) % <span class="built_in">this</span>.messageQueueList.size();</span><br><span class="line">            <span class="keyword">if</span> (pos &lt; <span class="number">0</span>)</span><br><span class="line">                pos = <span class="number">0</span>;</span><br><span class="line">            <span class="comment">//规避上次Broker队列</span></span><br><span class="line">            <span class="type">MessageQueue</span> <span class="variable">mq</span> <span class="operator">=</span> <span class="built_in">this</span>.messageQueueList.get(pos);</span><br><span class="line">            <span class="keyword">if</span> (!mq.getBrokerName().equals(lastBrokerName)) &#123;</span><br><span class="line">                <span class="keyword">return</span> mq;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//如果以上情况都不满足,返回sendWhichQueue取模后的队列</span></span><br><span class="line">        <span class="keyword">return</span> selectOneMessageQueue();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：TopicPublishInfo#selectOneMessageQueue()</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//第一次选择队列</span></span><br><span class="line"><span class="keyword">public</span> MessageQueue <span class="title function_">selectOneMessageQueue</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//sendWhichQueue自增</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> <span class="built_in">this</span>.sendWhichQueue.getAndIncrement();</span><br><span class="line">    <span class="comment">//对队列大小取模</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">pos</span> <span class="operator">=</span> Math.abs(index) % <span class="built_in">this</span>.messageQueueList.size();</span><br><span class="line">    <span class="keyword">if</span> (pos &lt; <span class="number">0</span>)</span><br><span class="line">        pos = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//返回对应的队列</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.messageQueueList.get(pos);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>启用Broker故障延迟机制</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> MessageQueue <span class="title function_">selectOneMessageQueue</span><span class="params">(<span class="keyword">final</span> TopicPublishInfo tpInfo, <span class="keyword">final</span> String lastBrokerName)</span> &#123;</span><br><span class="line">    <span class="comment">//Broker故障延迟机制</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.sendLatencyFaultEnable) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//对sendWhichQueue自增</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> tpInfo.getSendWhichQueue().getAndIncrement();</span><br><span class="line">            <span class="comment">//对消息队列轮询获取一个队列</span></span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; tpInfo.getMessageQueueList().size(); i++) &#123;</span><br><span class="line">                <span class="type">int</span> <span class="variable">pos</span> <span class="operator">=</span> Math.abs(index++) % tpInfo.getMessageQueueList().size();</span><br><span class="line">                <span class="keyword">if</span> (pos &lt; <span class="number">0</span>)</span><br><span class="line">                    pos = <span class="number">0</span>;</span><br><span class="line">                <span class="type">MessageQueue</span> <span class="variable">mq</span> <span class="operator">=</span> tpInfo.getMessageQueueList().get(pos);</span><br><span class="line">                <span class="comment">//验证该队列是否可用</span></span><br><span class="line">                <span class="keyword">if</span> (latencyFaultTolerance.isAvailable(mq.getBrokerName())) &#123;</span><br><span class="line">                    <span class="comment">//可用</span></span><br><span class="line">                    <span class="keyword">if</span> (<span class="literal">null</span> == lastBrokerName || mq.getBrokerName().equals(lastBrokerName))</span><br><span class="line">                        <span class="keyword">return</span> mq;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">//从规避的Broker中选择一个可用的Broker</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">String</span> <span class="variable">notBestBroker</span> <span class="operator">=</span> latencyFaultTolerance.pickOneAtLeast();</span><br><span class="line">            <span class="comment">//获得Broker的写队列集合</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">writeQueueNums</span> <span class="operator">=</span> tpInfo.getQueueIdByBroker(notBestBroker);</span><br><span class="line">            <span class="keyword">if</span> (writeQueueNums &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">//获得一个队列,指定broker和队列ID并返回</span></span><br><span class="line">                <span class="keyword">final</span> <span class="type">MessageQueue</span> <span class="variable">mq</span> <span class="operator">=</span> tpInfo.selectOneMessageQueue();</span><br><span class="line">                <span class="keyword">if</span> (notBestBroker != <span class="literal">null</span>) &#123;</span><br><span class="line">                    mq.setBrokerName(notBestBroker);</span><br><span class="line">                    mq.setQueueId(tpInfo.getSendWhichQueue().getAndIncrement() % writeQueueNums);</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> mq;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                latencyFaultTolerance.remove(notBestBroker);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;Error occurred when selecting message queue&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> tpInfo.selectOneMessageQueue();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> tpInfo.selectOneMessageQueue(lastBrokerName);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/Broker%E6%95%85%E9%9A%9C%E5%BB%B6%E8%BF%9F%E6%9C%BA%E5%88%B6%E6%A0%B8%E5%BF%83%E7%B1%BB.png"
                      alt="img"
                ></p>
<ul>
<li>延迟机制接口规范</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">LatencyFaultTolerance</span>&lt;T&gt; &#123;</span><br><span class="line">    <span class="comment">//更新失败条目</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">updateFaultItem</span><span class="params">(<span class="keyword">final</span> T name, <span class="keyword">final</span> <span class="type">long</span> currentLatency, <span class="keyword">final</span> <span class="type">long</span> notAvailableDuration)</span>;</span><br><span class="line">	<span class="comment">//判断Broker是否可用</span></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">isAvailable</span><span class="params">(<span class="keyword">final</span> T name)</span>;</span><br><span class="line">	<span class="comment">//移除Fault条目</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">remove</span><span class="params">(<span class="keyword">final</span> T name)</span>;</span><br><span class="line">	<span class="comment">//尝试从规避的Broker中选择一个可用的Broker</span></span><br><span class="line">    T <span class="title function_">pickOneAtLeast</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>FaultItem：失败条目</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FaultItem</span> <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;FaultItem&gt; &#123;</span><br><span class="line">    <span class="comment">//条目唯一键,这里为brokerName</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String name;</span><br><span class="line">    <span class="comment">//本次消息发送延迟</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> currentLatency;</span><br><span class="line">    <span class="comment">//故障规避开始时间</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> startTimestamp;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>消息失败策略</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MQFaultStrategy</span> &#123;</span><br><span class="line">   <span class="comment">//根据currentLatency本地消息发送延迟,从latencyMax尾部向前找到第一个比currentLatency小的索引,如果没有找到,返回0</span></span><br><span class="line">	<span class="keyword">private</span> <span class="type">long</span>[] latencyMax = &#123;<span class="number">50L</span>, <span class="number">100L</span>, <span class="number">550L</span>, <span class="number">1000L</span>, <span class="number">2000L</span>, <span class="number">3000L</span>, <span class="number">15000L</span>&#125;;</span><br><span class="line">    <span class="comment">//根据这个索引从notAvailableDuration取出对应的时间,在该时长内,Broker设置为不可用</span></span><br><span class="line">	<span class="keyword">private</span> <span class="type">long</span>[] notAvailableDuration = &#123;<span class="number">0L</span>, <span class="number">0L</span>, <span class="number">30000L</span>, <span class="number">60000L</span>, <span class="number">120000L</span>, <span class="number">180000L</span>, <span class="number">600000L</span>&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>原理分析</strong></em></p>
<p><em><strong>代码：DefaultMQProducerImpl#sendDefaultImpl</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sendResult = <span class="built_in">this</span>.sendKernelImpl(msg, </span><br><span class="line">                                 mq, </span><br><span class="line">                                 communicationMode, </span><br><span class="line">                                 sendCallback, </span><br><span class="line">                                 topicPublishInfo, </span><br><span class="line">                                 timeout - costTime);</span><br><span class="line">endTimestamp = System.currentTimeMillis();</span><br><span class="line"><span class="built_in">this</span>.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, <span class="literal">false</span>);</span><br></pre></td></tr></table></figure>



<p>如果上述发送过程出现异常，则调用<code>DefaultMQProducerImpl#updateFaultItem</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateFaultItem</span><span class="params">(<span class="keyword">final</span> String brokerName, <span class="keyword">final</span> <span class="type">long</span> currentLatency, <span class="type">boolean</span> isolation)</span> &#123;</span><br><span class="line">    <span class="comment">//参数一：broker名称</span></span><br><span class="line">    <span class="comment">//参数二:本次消息发送延迟时间</span></span><br><span class="line">    <span class="comment">//参数三:是否隔离</span></span><br><span class="line">    <span class="built_in">this</span>.mqFaultStrategy.updateFaultItem(brokerName, currentLatency, isolation);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQFaultStrategy#updateFaultItem</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateFaultItem</span><span class="params">(<span class="keyword">final</span> String brokerName, <span class="keyword">final</span> <span class="type">long</span> currentLatency, <span class="type">boolean</span> isolation)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.sendLatencyFaultEnable) &#123;</span><br><span class="line">        <span class="comment">//计算broker规避的时长</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">duration</span> <span class="operator">=</span> computeNotAvailableDuration(isolation ? <span class="number">30000</span> : currentLatency);</span><br><span class="line">        <span class="comment">//更新该FaultItem规避时长</span></span><br><span class="line">        <span class="built_in">this</span>.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQFaultStrategy#computeNotAvailableDuration</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">long</span> <span class="title function_">computeNotAvailableDuration</span><span class="params">(<span class="keyword">final</span> <span class="type">long</span> currentLatency)</span> &#123;</span><br><span class="line">    <span class="comment">//遍历latencyMax</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> latencyMax.length - <span class="number">1</span>; i &gt;= <span class="number">0</span>; i--) &#123;</span><br><span class="line">        <span class="comment">//找到第一个比currentLatency的latencyMax值</span></span><br><span class="line">        <span class="keyword">if</span> (currentLatency &gt;= latencyMax[i])</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">this</span>.notAvailableDuration[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//没有找到则返回0</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：LatencyFaultToleranceImpl#updateFaultItem</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateFaultItem</span><span class="params">(<span class="keyword">final</span> String name, <span class="keyword">final</span> <span class="type">long</span> currentLatency, <span class="keyword">final</span> <span class="type">long</span> notAvailableDuration)</span> &#123;</span><br><span class="line">    <span class="comment">//获得原FaultItem</span></span><br><span class="line">    <span class="type">FaultItem</span> <span class="variable">old</span> <span class="operator">=</span> <span class="built_in">this</span>.faultItemTable.get(name);</span><br><span class="line">    <span class="comment">//为空新建faultItem对象,设置规避时长和开始时间</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == old) &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">FaultItem</span> <span class="variable">faultItem</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FaultItem</span>(name);</span><br><span class="line">        faultItem.setCurrentLatency(currentLatency);</span><br><span class="line">        faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);</span><br><span class="line"></span><br><span class="line">        old = <span class="built_in">this</span>.faultItemTable.putIfAbsent(name, faultItem);</span><br><span class="line">        <span class="keyword">if</span> (old != <span class="literal">null</span>) &#123;</span><br><span class="line">            old.setCurrentLatency(currentLatency);</span><br><span class="line">            old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//更新规避时长和开始时间</span></span><br><span class="line">        old.setCurrentLatency(currentLatency);</span><br><span class="line">        old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>####4）发送消息</p>
<p>消息发送API核心入口<em><strong>DefaultMQProducerImpl#sendKernelImpl</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> SendResult <span class="title function_">sendKernelImpl</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> Message msg,	//待发送消息</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> MessageQueue mq,	//消息发送队列</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> CommunicationMode communicationMode,		//消息发送内模式</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> SendCallback sendCallback,	pp	//异步消息回调函数</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> TopicPublishInfo topicPublishInfo,	//主题路由信息</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> <span class="type">long</span> timeout	//超时时间</span></span><br><span class="line"><span class="params">    )</span></span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMQProducerImpl#sendKernelImpl</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//获得broker网络地址信息</span></span><br><span class="line"><span class="type">String</span> <span class="variable">brokerAddr</span> <span class="operator">=</span> <span class="built_in">this</span>.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> == brokerAddr) &#123;</span><br><span class="line">    <span class="comment">//没有找到从NameServer更新broker网络地址信息</span></span><br><span class="line">    tryToFindTopicPublishInfo(mq.getTopic());</span><br><span class="line">    brokerAddr = <span class="built_in">this</span>.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//为消息分类唯一ID</span></span><br><span class="line"><span class="keyword">if</span> (!(msg <span class="keyword">instanceof</span> MessageBatch)) &#123;</span><br><span class="line">    MessageClientIDSetter.setUniqID(msg);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="variable">topicWithNamespace</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> != <span class="built_in">this</span>.mQClientFactory.getClientConfig().getNamespace()) &#123;</span><br><span class="line">    msg.setInstanceId(<span class="built_in">this</span>.mQClientFactory.getClientConfig().getNamespace());</span><br><span class="line">    topicWithNamespace = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//消息大小超过4K,启用消息压缩</span></span><br><span class="line"><span class="type">int</span> <span class="variable">sysFlag</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="type">boolean</span> <span class="variable">msgBodyCompressed</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.tryToCompressMessage(msg)) &#123;</span><br><span class="line">    sysFlag |= MessageSysFlag.COMPRESSED_FLAG;</span><br><span class="line">    msgBodyCompressed = <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//如果是事务消息,设置消息标记MessageSysFlag.TRANSACTION_PREPARED_TYPE</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">String</span> <span class="variable">tranMsg</span> <span class="operator">=</span> msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);</span><br><span class="line"><span class="keyword">if</span> (tranMsg != <span class="literal">null</span> &amp;&amp; Boolean.parseBoolean(tranMsg)) &#123;</span><br><span class="line">    sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//如果注册了消息发送钩子函数,在执行消息发送前的增强逻辑</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.hasSendMessageHook()) &#123;</span><br><span class="line">    context = <span class="keyword">new</span> <span class="title class_">SendMessageContext</span>();</span><br><span class="line">    context.setProducer(<span class="built_in">this</span>);</span><br><span class="line">    context.setProducerGroup(<span class="built_in">this</span>.defaultMQProducer.getProducerGroup());</span><br><span class="line">    context.setCommunicationMode(communicationMode);</span><br><span class="line">    context.setBornHost(<span class="built_in">this</span>.defaultMQProducer.getClientIP());</span><br><span class="line">    context.setBrokerAddr(brokerAddr);</span><br><span class="line">    context.setMessage(msg);</span><br><span class="line">    context.setMq(mq);</span><br><span class="line">    context.setNamespace(<span class="built_in">this</span>.defaultMQProducer.getNamespace());</span><br><span class="line">    <span class="type">String</span> <span class="variable">isTrans</span> <span class="operator">=</span> msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);</span><br><span class="line">    <span class="keyword">if</span> (isTrans != <span class="literal">null</span> &amp;&amp; isTrans.equals(<span class="string">&quot;true&quot;</span>)) &#123;</span><br><span class="line">        context.setMsgType(MessageType.Trans_Msg_Half);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (msg.getProperty(<span class="string">&quot;__STARTDELIVERTIME&quot;</span>) != <span class="literal">null</span> || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != <span class="literal">null</span>) &#123;</span><br><span class="line">        context.setMsgType(MessageType.Delay_Msg);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.executeSendMessageHookBefore(context);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：SendMessageHook</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">SendMessageHook</span> &#123;</span><br><span class="line">    String <span class="title function_">hookName</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">sendMessageBefore</span><span class="params">(<span class="keyword">final</span> SendMessageContext context)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">sendMessageAfter</span><span class="params">(<span class="keyword">final</span> SendMessageContext context)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMQProducerImpl#sendKernelImpl</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//构建消息发送请求包</span></span><br><span class="line"><span class="type">SendMessageRequestHeader</span> <span class="variable">requestHeader</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SendMessageRequestHeader</span>();</span><br><span class="line"><span class="comment">//生产者组</span></span><br><span class="line">requestHeader.setProducerGroup(<span class="built_in">this</span>.defaultMQProducer.getProducerGroup());</span><br><span class="line"><span class="comment">//主题</span></span><br><span class="line">requestHeader.setTopic(msg.getTopic());</span><br><span class="line"><span class="comment">//默认创建主题Key</span></span><br><span class="line">requestHeader.setDefaultTopic(<span class="built_in">this</span>.defaultMQProducer.getCreateTopicKey());</span><br><span class="line"><span class="comment">//该主题在单个Broker默认队列树</span></span><br><span class="line">requestHeader.setDefaultTopicQueueNums(<span class="built_in">this</span>.defaultMQProducer.getDefaultTopicQueueNums());</span><br><span class="line"><span class="comment">//队列ID</span></span><br><span class="line">requestHeader.setQueueId(mq.getQueueId());</span><br><span class="line"><span class="comment">//消息系统标记</span></span><br><span class="line">requestHeader.setSysFlag(sysFlag);</span><br><span class="line"><span class="comment">//消息发送时间</span></span><br><span class="line">requestHeader.setBornTimestamp(System.currentTimeMillis());</span><br><span class="line"><span class="comment">//消息标记</span></span><br><span class="line">requestHeader.setFlag(msg.getFlag());</span><br><span class="line"><span class="comment">//消息扩展信息</span></span><br><span class="line">requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));</span><br><span class="line"><span class="comment">//消息重试次数</span></span><br><span class="line">requestHeader.setReconsumeTimes(<span class="number">0</span>);</span><br><span class="line">requestHeader.setUnitMode(<span class="built_in">this</span>.isUnitMode());</span><br><span class="line"><span class="comment">//是否是批量消息等</span></span><br><span class="line">requestHeader.setBatch(msg <span class="keyword">instanceof</span> MessageBatch);</span><br><span class="line"><span class="keyword">if</span> (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">reconsumeTimes</span> <span class="operator">=</span> MessageAccessor.getReconsumeTime(msg);</span><br><span class="line">    <span class="keyword">if</span> (reconsumeTimes != <span class="literal">null</span>) &#123;</span><br><span class="line">        requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));</span><br><span class="line">        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">maxReconsumeTimes</span> <span class="operator">=</span> MessageAccessor.getMaxReconsumeTimes(msg);</span><br><span class="line">    <span class="keyword">if</span> (maxReconsumeTimes != <span class="literal">null</span>) &#123;</span><br><span class="line">        requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));</span><br><span class="line">        MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> ASYNC:		<span class="comment">//异步发送</span></span><br><span class="line">    <span class="type">Message</span> <span class="variable">tmpMessage</span> <span class="operator">=</span> msg;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">messageCloned</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">if</span> (msgBodyCompressed) &#123;</span><br><span class="line">        <span class="comment">//If msg body was compressed, msgbody should be reset using prevBody.</span></span><br><span class="line">        <span class="comment">//Clone new message using commpressed message body and recover origin massage.</span></span><br><span class="line">        <span class="comment">//Fix bug:https://github.com/apache/rocketmq-externals/issues/66</span></span><br><span class="line">        tmpMessage = MessageAccessor.cloneMessage(msg);</span><br><span class="line">        messageCloned = <span class="literal">true</span>;</span><br><span class="line">        msg.setBody(prevBody);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (topicWithNamespace) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!messageCloned) &#123;</span><br><span class="line">            tmpMessage = MessageAccessor.cloneMessage(msg);</span><br><span class="line">            messageCloned = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), </span><br><span class="line">                                                    <span class="built_in">this</span>.defaultMQProducer.getNamespace()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">		<span class="type">long</span> <span class="variable">costTimeAsync</span> <span class="operator">=</span> System.currentTimeMillis() - beginStartTime;</span><br><span class="line">		<span class="keyword">if</span> (timeout &lt; costTimeAsync) &#123;</span><br><span class="line">		    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RemotingTooMuchRequestException</span>(<span class="string">&quot;sendKernelImpl call timeout&quot;</span>);</span><br><span class="line">		&#125;</span><br><span class="line">		sendResult = <span class="built_in">this</span>.mQClientFactory.getMQClientAPIImpl().sendMessage(</span><br><span class="line">        			brokerAddr,</span><br><span class="line">        			mq.getBrokerName(),</span><br><span class="line">        			tmpMessage,</span><br><span class="line">        			requestHeader,</span><br><span class="line">        			timeout - costTimeAsync,</span><br><span class="line">        			communicationMode,</span><br><span class="line">        			sendCallback,</span><br><span class="line">        			topicPublishInfo,</span><br><span class="line">        			<span class="built_in">this</span>.mQClientFactory,</span><br><span class="line">        			<span class="built_in">this</span>.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),</span><br><span class="line">        			context,</span><br><span class="line">        			<span class="built_in">this</span>);</span><br><span class="line">    	<span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> ONEWAY:</span><br><span class="line"><span class="keyword">case</span> SYNC:		<span class="comment">//同步发送</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">costTimeSync</span> <span class="operator">=</span> System.currentTimeMillis() - beginStartTime;</span><br><span class="line">        <span class="keyword">if</span> (timeout &lt; costTimeSync) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RemotingTooMuchRequestException</span>(<span class="string">&quot;sendKernelImpl call timeout&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        sendResult = <span class="built_in">this</span>.mQClientFactory.getMQClientAPIImpl().sendMessage(</span><br><span class="line">            brokerAddr,</span><br><span class="line">            mq.getBrokerName(),</span><br><span class="line">            msg,</span><br><span class="line">            requestHeader,</span><br><span class="line">            timeout - costTimeSync,</span><br><span class="line">            communicationMode,</span><br><span class="line">            context,</span><br><span class="line">            <span class="built_in">this</span>);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="keyword">assert</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>




<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//如果注册了钩子函数,则发送完毕后执行钩子函数</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.hasSendMessageHook()) &#123;</span><br><span class="line">    context.setSendResult(sendResult);</span><br><span class="line">    <span class="built_in">this</span>.executeSendMessageHookAfter(context);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-3-4-批量消息发送"><a href="#2-3-4-批量消息发送" class="headerlink" title="2.3.4 批量消息发送"></a>2.3.4 批量消息发送</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%8F%91%E9%80%81%E6%89%B9%E9%87%8F%E6%B6%88%E6%81%AF.png"
                      alt="img"
                ></p>
<p>批量消息发送是将同一个主题的多条消息一起打包发送到消息服务端，减少网络调用次数，提高网络传输效率。当然，并不是在同一批次中发送的消息数量越多越好，其判断依据是单条消息的长度，如果单条消息内容比较长，则打包多条消息发送会影响其他线程发送消息的响应时间，并且单批次消息总长度不能超过DefaultMQProducer#maxMessageSize。</p>
<p>批量消息发送要解决的问题是如何将这些消息编码以便服务端能够正确解码出每条消息的消息内容。</p>
<p><em><strong>代码：DefaultMQProducer#send</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> SendResult <span class="title function_">send</span><span class="params">(Collection&lt;Message&gt; msgs)</span> </span><br><span class="line">    <span class="keyword">throws</span> MQClientException, RemotingException, MQBrokerException, InterruptedException &#123;</span><br><span class="line">    <span class="comment">//压缩消息集合成一条消息,然后发送出去</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.defaultMQProducerImpl.send(batch(msgs));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMQProducer#batch</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> MessageBatch <span class="title function_">batch</span><span class="params">(Collection&lt;Message&gt; msgs)</span> <span class="keyword">throws</span> MQClientException &#123;</span><br><span class="line">    MessageBatch msgBatch;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//将集合消息封装到MessageBatch</span></span><br><span class="line">        msgBatch = MessageBatch.generateFromList(msgs);</span><br><span class="line">        <span class="comment">//遍历消息集合,检查消息合法性,设置消息ID,设置Topic</span></span><br><span class="line">        <span class="keyword">for</span> (Message message : msgBatch) &#123;</span><br><span class="line">            Validators.checkMessage(message, <span class="built_in">this</span>);</span><br><span class="line">            MessageClientIDSetter.setUniqID(message);</span><br><span class="line">            message.setTopic(withNamespace(message.getTopic()));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//压缩消息,设置消息body</span></span><br><span class="line">        msgBatch.setBody(msgBatch.encode());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(<span class="string">&quot;Failed to initiate the MessageBatch&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//设置msgBatch的topic</span></span><br><span class="line">    msgBatch.setTopic(withNamespace(msgBatch.getTopic()));</span><br><span class="line">    <span class="keyword">return</span> msgBatch;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="2-4-消息存储"><a href="#2-4-消息存储" class="headerlink" title="2.4 消息存储"></a>2.4 消息存储</h2><p>###2.4.1 消息存储核心类</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/DefaultMessageStore.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> MessageStoreConfig messageStoreConfig;	<span class="comment">//消息配置属性</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> CommitLog commitLog;		<span class="comment">//CommitLog文件存储的实现类</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ConcurrentMap&lt;String<span class="comment">/* topic */</span>, ConcurrentMap&lt;Integer<span class="comment">/* queueId */</span>, ConsumeQueue&gt;&gt; consumeQueueTable;	<span class="comment">//消息队列存储缓存表,按照消息主题分组</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> FlushConsumeQueueService flushConsumeQueueService;	<span class="comment">//消息队列文件刷盘线程</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> CleanCommitLogService cleanCommitLogService;	<span class="comment">//清除CommitLog文件服务</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> CleanConsumeQueueService cleanConsumeQueueService;	<span class="comment">//清除ConsumerQueue队列文件服务</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> IndexService indexService;	<span class="comment">//索引实现类</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> AllocateMappedFileService allocateMappedFileService;	<span class="comment">//MappedFile分配服务</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ReputMessageService reputMessageService;<span class="comment">//CommitLog消息分发,根据CommitLog文件构建ConsumerQueue、IndexFile文件</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HAService haService;	<span class="comment">//存储HA机制</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ScheduleMessageService scheduleMessageService;	<span class="comment">//消息服务调度线程</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> StoreStatsService storeStatsService;	<span class="comment">//消息存储服务</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> TransientStorePool transientStorePool;	<span class="comment">//消息堆外内存缓存</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> BrokerStatsManager brokerStatsManager;	<span class="comment">//Broker状态管理器</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> MessageArrivingListener messageArrivingListener;	<span class="comment">//消息拉取长轮询模式消息达到监听器</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> BrokerConfig brokerConfig;	<span class="comment">//Broker配置类</span></span><br><span class="line"><span class="keyword">private</span> StoreCheckpoint storeCheckpoint;	<span class="comment">//文件刷盘监测点</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> LinkedList&lt;CommitLogDispatcher&gt; dispatcherList;	<span class="comment">//CommitLog文件转发请求</span></span><br></pre></td></tr></table></figure>



<h3 id="2-4-2-消息存储流程"><a href="#2-4-2-消息存储流程" class="headerlink" title="2.4.2 消息存储流程"></a>2.4.2 消息存储流程</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p><em><strong>消息存储入口：DefaultMessageStore#putMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//判断Broker角色如果是从节点,则无需写入</span></span><br><span class="line"><span class="keyword">if</span> (BrokerRole.SLAVE == <span class="built_in">this</span>.messageStoreConfig.getBrokerRole()) &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">value</span> <span class="operator">=</span> <span class="built_in">this</span>.printTimes.getAndIncrement();</span><br><span class="line">        <span class="keyword">if</span> ((value % <span class="number">50000</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">            log.warn(<span class="string">&quot;message store is slave mode, so putMessage is forbidden &quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.SERVICE_NOT_AVAILABLE, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//判断当前写入状态如果是正在写入,则不能继续</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="built_in">this</span>.runningFlags.isWriteable()) &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">value</span> <span class="operator">=</span> <span class="built_in">this</span>.printTimes.getAndIncrement();</span><br><span class="line">    	<span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.SERVICE_NOT_AVAILABLE, <span class="literal">null</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.printTimes.set(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//判断消息主题长度是否超过最大限制</span></span><br><span class="line"><span class="keyword">if</span> (msg.getTopic().length() &gt; Byte.MAX_VALUE) &#123;</span><br><span class="line">    log.warn(<span class="string">&quot;putMessage message topic length too long &quot;</span> + msg.getTopic().length());</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.MESSAGE_ILLEGAL, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//判断消息属性长度是否超过限制</span></span><br><span class="line"><span class="keyword">if</span> (msg.getPropertiesString() != <span class="literal">null</span> &amp;&amp; msg.getPropertiesString().length() &gt; Short.MAX_VALUE) &#123;</span><br><span class="line">    log.warn(<span class="string">&quot;putMessage message properties length too long &quot;</span> + msg.getPropertiesString().length());</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.PROPERTIES_SIZE_EXCEEDED, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//判断系统PageCache缓存去是否占用</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.isOSPageCacheBusy()) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.OS_PAGECACHE_BUSY, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//将消息写入CommitLog文件</span></span><br><span class="line"><span class="type">PutMessageResult</span> <span class="variable">result</span> <span class="operator">=</span> <span class="built_in">this</span>.commitLog.putMessage(msg);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：CommitLog#putMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//记录消息存储时间</span></span><br><span class="line">msg.setStoreTimestamp(beginLockTimestamp);</span><br><span class="line"></span><br><span class="line"><span class="comment">//判断如果mappedFile如果为空或者已满,创建新的mappedFile文件</span></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> == mappedFile || mappedFile.isFull()) &#123;</span><br><span class="line">    mappedFile = <span class="built_in">this</span>.mappedFileQueue.getLastMappedFile(<span class="number">0</span>); </span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//如果创建失败,直接返回</span></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> == mappedFile) &#123;</span><br><span class="line">    log.error(<span class="string">&quot;create mapped file1 error, topic: &quot;</span> + msg.getTopic() + <span class="string">&quot; clientAddr: &quot;</span> + msg.getBornHostString());</span><br><span class="line">    beginTimeInLock = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PutMessageResult</span>(PutMessageStatus.CREATE_MAPEDFILE_FAILED, <span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//写入消息到mappedFile中</span></span><br><span class="line">result = mappedFile.appendMessage(msg, <span class="built_in">this</span>.appendMessageCallback);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MappedFile#appendMessagesInner</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//获得文件的写入指针</span></span><br><span class="line"><span class="type">int</span> <span class="variable">currentPos</span> <span class="operator">=</span> <span class="built_in">this</span>.wrotePosition.get();</span><br><span class="line"></span><br><span class="line"><span class="comment">//如果指针大于文件大小则直接返回</span></span><br><span class="line"><span class="keyword">if</span> (currentPos &lt; <span class="built_in">this</span>.fileSize) &#123;</span><br><span class="line">    <span class="comment">//通过writeBuffer.slice()创建一个与MappedFile共享的内存区,并设置position为当前指针</span></span><br><span class="line">    <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> writeBuffer != <span class="literal">null</span> ? writeBuffer.slice() : <span class="built_in">this</span>.mappedByteBuffer.slice();</span><br><span class="line">    byteBuffer.position(currentPos);</span><br><span class="line">    <span class="type">AppendMessageResult</span> <span class="variable">result</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (messageExt <span class="keyword">instanceof</span> MessageExtBrokerInner) &#123;</span><br><span class="line">       	<span class="comment">//通过回调方法写入</span></span><br><span class="line">        result = cb.doAppend(<span class="built_in">this</span>.getFileFromOffset(), byteBuffer, <span class="built_in">this</span>.fileSize - currentPos, (MessageExtBrokerInner) messageExt);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (messageExt <span class="keyword">instanceof</span> MessageExtBatch) &#123;</span><br><span class="line">        result = cb.doAppend(<span class="built_in">this</span>.getFileFromOffset(), byteBuffer, <span class="built_in">this</span>.fileSize - currentPos, (MessageExtBatch) messageExt);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AppendMessageResult</span>(AppendMessageStatus.UNKNOWN_ERROR);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.wrotePosition.addAndGet(result.getWroteBytes());</span><br><span class="line">    <span class="built_in">this</span>.storeTimestamp = result.getStoreTimestamp();</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：CommitLog#doAppend</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//文件写入位置</span></span><br><span class="line"><span class="type">long</span> <span class="variable">wroteOffset</span> <span class="operator">=</span> fileFromOffset + byteBuffer.position();</span><br><span class="line"><span class="comment">//设置消息ID</span></span><br><span class="line"><span class="built_in">this</span>.resetByteBuffer(hostHolder, <span class="number">8</span>);</span><br><span class="line"><span class="type">String</span> <span class="variable">msgId</span> <span class="operator">=</span> MessageDecoder.createMessageId(<span class="built_in">this</span>.msgIdMemory, msgInner.getStoreHostBytes(hostHolder), wroteOffset);</span><br><span class="line"></span><br><span class="line"><span class="comment">//获得该消息在消息队列中的偏移量</span></span><br><span class="line">keyBuilder.setLength(<span class="number">0</span>);</span><br><span class="line">keyBuilder.append(msgInner.getTopic());</span><br><span class="line">keyBuilder.append(<span class="string">&#x27;-&#x27;</span>);</span><br><span class="line">keyBuilder.append(msgInner.getQueueId());</span><br><span class="line"><span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyBuilder.toString();</span><br><span class="line"><span class="type">Long</span> <span class="variable">queueOffset</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.topicQueueTable.get(key);</span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> == queueOffset) &#123;</span><br><span class="line">    queueOffset = <span class="number">0L</span>;</span><br><span class="line">    CommitLog.<span class="built_in">this</span>.topicQueueTable.put(key, queueOffset);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获得消息属性长度</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">byte</span>[] propertiesData =msgInner.getPropertiesString() == <span class="literal">null</span> ? <span class="literal">null</span> : msgInner.getPropertiesString().getBytes(MessageDecoder.CHARSET_UTF8);</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">propertiesLength</span> <span class="operator">=</span> propertiesData == <span class="literal">null</span> ? <span class="number">0</span> : propertiesData.length;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (propertiesLength &gt; Short.MAX_VALUE) &#123;</span><br><span class="line">    log.warn(<span class="string">&quot;putMessage message properties length too long. length=&#123;&#125;&quot;</span>, propertiesData.length);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AppendMessageResult</span>(AppendMessageStatus.PROPERTIES_SIZE_EXCEEDED);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获得消息主题大小</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">byte</span>[] topicData = msgInner.getTopic().getBytes(MessageDecoder.CHARSET_UTF8);</span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">topicLength</span> <span class="operator">=</span> topicData.length;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获得消息体大小</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">bodyLength</span> <span class="operator">=</span> msgInner.getBody() == <span class="literal">null</span> ? <span class="number">0</span> : msgInner.getBody().length;</span><br><span class="line"><span class="comment">//计算消息总长度</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">msgLen</span> <span class="operator">=</span> calMsgLength(bodyLength, topicLength, propertiesLength);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：CommitLog#calMsgLength</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">calMsgLength</span><span class="params">(<span class="type">int</span> bodyLength, <span class="type">int</span> topicLength, <span class="type">int</span> propertiesLength)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">msgLen</span> <span class="operator">=</span> <span class="number">4</span> <span class="comment">//TOTALSIZE</span></span><br><span class="line">        + <span class="number">4</span> <span class="comment">//MAGICCODE  </span></span><br><span class="line">        + <span class="number">4</span> <span class="comment">//BODYCRC</span></span><br><span class="line">        + <span class="number">4</span> <span class="comment">//QUEUEID</span></span><br><span class="line">        + <span class="number">4</span> <span class="comment">//FLAG</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//QUEUEOFFSET</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//PHYSICALOFFSET</span></span><br><span class="line">        + <span class="number">4</span> <span class="comment">//SYSFLAG</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//BORNTIMESTAMP</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//BORNHOST</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//STORETIMESTAMP</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//STOREHOSTADDRESS</span></span><br><span class="line">        + <span class="number">4</span> <span class="comment">//RECONSUMETIMES</span></span><br><span class="line">        + <span class="number">8</span> <span class="comment">//Prepared Transaction Offset</span></span><br><span class="line">        + <span class="number">4</span> + (bodyLength &gt; <span class="number">0</span> ? bodyLength : <span class="number">0</span>) <span class="comment">//BODY</span></span><br><span class="line">        + <span class="number">1</span> + topicLength <span class="comment">//TOPIC</span></span><br><span class="line">        + <span class="number">2</span> + (propertiesLength &gt; <span class="number">0</span> ? propertiesLength : <span class="number">0</span>) <span class="comment">//propertiesLength</span></span><br><span class="line">        + <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> msgLen;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：CommitLog#doAppend</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//消息长度不能超过4M</span></span><br><span class="line"><span class="keyword">if</span> (msgLen &gt; <span class="built_in">this</span>.maxMessageSize) &#123;</span><br><span class="line">    CommitLog.log.warn(<span class="string">&quot;message size exceeded, msg total size: &quot;</span> + msgLen + <span class="string">&quot;, msg body size: &quot;</span> + bodyLength</span><br><span class="line">        + <span class="string">&quot;, maxMessageSize: &quot;</span> + <span class="built_in">this</span>.maxMessageSize);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AppendMessageResult</span>(AppendMessageStatus.MESSAGE_SIZE_EXCEEDED);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//消息是如果没有足够的存储空间则新创建CommitLog文件</span></span><br><span class="line"><span class="keyword">if</span> ((msgLen + END_FILE_MIN_BLANK_LENGTH) &gt; maxBlank) &#123;</span><br><span class="line">    <span class="built_in">this</span>.resetByteBuffer(<span class="built_in">this</span>.msgStoreItemMemory, maxBlank);</span><br><span class="line">    <span class="comment">// 1 TOTALSIZE</span></span><br><span class="line">    <span class="built_in">this</span>.msgStoreItemMemory.putInt(maxBlank);</span><br><span class="line">    <span class="comment">// 2 MAGICCODE</span></span><br><span class="line">    <span class="built_in">this</span>.msgStoreItemMemory.putInt(CommitLog.BLANK_MAGIC_CODE);</span><br><span class="line">    <span class="comment">// 3 The remaining space may be any value</span></span><br><span class="line">    <span class="comment">// Here the length of the specially set maxBlank</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">long</span> <span class="variable">beginTimeMills</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.now();</span><br><span class="line">    byteBuffer.put(<span class="built_in">this</span>.msgStoreItemMemory.array(), <span class="number">0</span>, maxBlank);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AppendMessageResult</span>(AppendMessageStatus.END_OF_FILE, wroteOffset, maxBlank, msgId, msgInner.getStoreTimestamp(),</span><br><span class="line">        queueOffset, CommitLog.<span class="built_in">this</span>.defaultMessageStore.now() - beginTimeMills);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//将消息存储到ByteBuffer中,返回AppendMessageResult</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">long</span> <span class="variable">beginTimeMills</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.now();</span><br><span class="line"><span class="comment">// Write messages to the queue buffer</span></span><br><span class="line">byteBuffer.put(<span class="built_in">this</span>.msgStoreItemMemory.array(), <span class="number">0</span>, msgLen);</span><br><span class="line"><span class="type">AppendMessageResult</span> <span class="variable">result</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AppendMessageResult</span>(AppendMessageStatus.PUT_OK, wroteOffset, </span><br><span class="line">                                                     msgLen, msgId,msgInner.getStoreTimestamp(), </span><br><span class="line">                                                     queueOffset, </span><br><span class="line">                                                     CommitLog.<span class="built_in">this</span>.defaultMessageStore.now() </span><br><span class="line">                                                     -beginTimeMills);</span><br><span class="line"><span class="keyword">switch</span> (tranType) &#123;</span><br><span class="line">    <span class="keyword">case</span> MessageSysFlag.TRANSACTION_PREPARED_TYPE:</span><br><span class="line">    <span class="keyword">case</span> MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> MessageSysFlag.TRANSACTION_NOT_TYPE:</span><br><span class="line">    <span class="keyword">case</span> MessageSysFlag.TRANSACTION_COMMIT_TYPE:</span><br><span class="line">        <span class="comment">//更新消息队列偏移量</span></span><br><span class="line">        CommitLog.<span class="built_in">this</span>.topicQueueTable.put(key, ++queueOffset);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：CommitLog#putMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//释放锁</span></span><br><span class="line">putMessageLock.unlock();</span><br><span class="line"><span class="comment">//刷盘</span></span><br><span class="line">handleDiskFlush(result, putMessageResult, msg);</span><br><span class="line"><span class="comment">//执行HA主从同步</span></span><br><span class="line">handleHA(result, putMessageResult, msg);</span><br></pre></td></tr></table></figure>



<h3 id="2-4-3-存储文件"><a href="#2-4-3-存储文件" class="headerlink" title="2.4.3 存储文件"></a>2.4.3 存储文件</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%AD%98%E5%82%A8%E6%96%87%E4%BB%B6.png"
                      alt="img"
                ></p>
<ul>
<li>commitLog：消息存储目录</li>
<li>config：运行期间一些配置信息</li>
<li>consumerqueue：消息消费队列存储目录</li>
<li>index：消息索引文件存储目录</li>
<li>abort：如果存在改文件寿命Broker非正常关闭</li>
<li>checkpoint：文件检查点，存储CommitLog文件最后一次刷盘时间戳、consumerquueue最后一次刷盘时间，index索引文件最后一次刷盘时间戳。</li>
</ul>
<h3 id="2-4-4-存储文件内存映射"><a href="#2-4-4-存储文件内存映射" class="headerlink" title="2.4.4 存储文件内存映射"></a>2.4.4 存储文件内存映射</h3><p>RocketMQ通过使用内存映射文件提高IO访问性能，无论是CommitLog、ConsumerQueue还是IndexFile，单个文件都被设计为固定长度，如果一个文件写满以后再创建一个新文件，文件名就为该文件第一条消息对应的全局物理偏移量。</p>
<p>####1）MappedFileQueue</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/MappedFileQueue.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">String storePath;	<span class="comment">//存储目录</span></span><br><span class="line"><span class="type">int</span> mappedFileSize;	<span class="comment">// 单个文件大小</span></span><br><span class="line">CopyOnWriteArrayList&lt;MappedFile&gt; mappedFiles;	<span class="comment">//MappedFile文件集合</span></span><br><span class="line">AllocateMappedFileService allocateMappedFileService;	<span class="comment">//创建MapFile服务类</span></span><br><span class="line"><span class="type">long</span> <span class="variable">flushedWhere</span> <span class="operator">=</span> <span class="number">0</span>;		<span class="comment">//当前刷盘指针</span></span><br><span class="line"><span class="type">long</span> <span class="variable">committedWhere</span> <span class="operator">=</span> <span class="number">0</span>;	<span class="comment">//当前数据提交指针,内存中ByteBuffer当前的写指针,该值大于等于flushWhere</span></span><br></pre></td></tr></table></figure>



<ul>
<li>根据存储时间查询MappedFile</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> MappedFile <span class="title function_">getMappedFileByTime</span><span class="params">(<span class="keyword">final</span> <span class="type">long</span> timestamp)</span> &#123;</span><br><span class="line">    Object[] mfs = <span class="built_in">this</span>.copyMappedFiles(<span class="number">0</span>);</span><br><span class="line">	</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == mfs)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">	<span class="comment">//遍历MappedFile文件数组</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; mfs.length; i++) &#123;</span><br><span class="line">        <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> (MappedFile) mfs[i];</span><br><span class="line">        <span class="comment">//MappedFile文件的最后修改时间大于指定时间戳则返回该文件</span></span><br><span class="line">        <span class="keyword">if</span> (mappedFile.getLastModifiedTimestamp() &gt;= timestamp) &#123;</span><br><span class="line">            <span class="keyword">return</span> mappedFile;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> (MappedFile) mfs[mfs.length - <span class="number">1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>根据消息偏移量offset查找MappedFile</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> MappedFile <span class="title function_">findMappedFileByOffset</span><span class="params">(<span class="keyword">final</span> <span class="type">long</span> offset, <span class="keyword">final</span> <span class="type">boolean</span> returnFirstOnNotFound)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">//获得第一个MappedFile文件</span></span><br><span class="line">        <span class="type">MappedFile</span> <span class="variable">firstMappedFile</span> <span class="operator">=</span> <span class="built_in">this</span>.getFirstMappedFile();</span><br><span class="line">        <span class="comment">//获得最后一个MappedFile文件</span></span><br><span class="line">        <span class="type">MappedFile</span> <span class="variable">lastMappedFile</span> <span class="operator">=</span> <span class="built_in">this</span>.getLastMappedFile();</span><br><span class="line">        <span class="comment">//第一个文件和最后一个文件均不为空,则进行处理</span></span><br><span class="line">        <span class="keyword">if</span> (firstMappedFile != <span class="literal">null</span> &amp;&amp; lastMappedFile != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (offset &lt; firstMappedFile.getFileFromOffset() || </span><br><span class="line">                offset &gt;= lastMappedFile.getFileFromOffset() + <span class="built_in">this</span>.mappedFileSize) &#123;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">//获得文件索引</span></span><br><span class="line">                <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> (<span class="type">int</span>) ((offset / <span class="built_in">this</span>.mappedFileSize) </span><br><span class="line">                                   - (firstMappedFile.getFileFromOffset() / <span class="built_in">this</span>.mappedFileSize));</span><br><span class="line">                <span class="type">MappedFile</span> <span class="variable">targetFile</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">//根据索引返回目标文件</span></span><br><span class="line">                    targetFile = <span class="built_in">this</span>.mappedFiles.get(index);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception ignored) &#123;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (targetFile != <span class="literal">null</span> &amp;&amp; offset &gt;= targetFile.getFileFromOffset()</span><br><span class="line">                    &amp;&amp; offset &lt; targetFile.getFileFromOffset() + <span class="built_in">this</span>.mappedFileSize) &#123;</span><br><span class="line">                    <span class="keyword">return</span> targetFile;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                <span class="keyword">for</span> (MappedFile tmpMappedFile : <span class="built_in">this</span>.mappedFiles) &#123;</span><br><span class="line">                    <span class="keyword">if</span> (offset &gt;= tmpMappedFile.getFileFromOffset()</span><br><span class="line">                        &amp;&amp; offset &lt; tmpMappedFile.getFileFromOffset() + <span class="built_in">this</span>.mappedFileSize) &#123;</span><br><span class="line">                        <span class="keyword">return</span> tmpMappedFile;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (returnFirstOnNotFound) &#123;</span><br><span class="line">                <span class="keyword">return</span> firstMappedFile;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;findMappedFileByOffset Exception&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>获取存储文件最小偏移量</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMinOffset</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">this</span>.mappedFiles.isEmpty()) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">this</span>.mappedFiles.get(<span class="number">0</span>).getFileFromOffset();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IndexOutOfBoundsException e) &#123;</span><br><span class="line">            <span class="comment">//continue;</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;getMinOffset has exception.&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>获取存储文件最大偏移量</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMaxOffset</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> getLastMappedFile();</span><br><span class="line">    <span class="keyword">if</span> (mappedFile != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> mappedFile.getFileFromOffset() + mappedFile.getReadPosition();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<ul>
<li>返回存储文件当前写指针</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMaxWrotePosition</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> getLastMappedFile();</span><br><span class="line">    <span class="keyword">if</span> (mappedFile != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> mappedFile.getFileFromOffset() + mappedFile.getWrotePosition();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>####2）MappedFile</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/MappedFile.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">OS_PAGE_SIZE</span> <span class="operator">=</span> <span class="number">1024</span> * <span class="number">4</span>;		<span class="comment">//操作系统每页大小,默认4K</span></span><br><span class="line"><span class="type">AtomicLong</span> <span class="variable">TOTAL_MAPPED_VIRTUAL_MEMORY</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicLong</span>(<span class="number">0</span>);	<span class="comment">//当前JVM实例中MappedFile虚拟内存</span></span><br><span class="line"><span class="type">AtomicInteger</span> <span class="variable">TOTAL_MAPPED_FILES</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);	<span class="comment">//当前JVM实例中MappedFile对象个数</span></span><br><span class="line"><span class="type">AtomicInteger</span> <span class="variable">wrotePosition</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);	<span class="comment">//当前文件的写指针</span></span><br><span class="line"><span class="type">AtomicInteger</span> <span class="variable">committedPosition</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);	<span class="comment">//当前文件的提交指针</span></span><br><span class="line"><span class="type">AtomicInteger</span> <span class="variable">flushedPosition</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</span>);	<span class="comment">//刷写到磁盘指针</span></span><br><span class="line"><span class="type">int</span> fileSize;	<span class="comment">//文件大小</span></span><br><span class="line">FileChannel fileChannel;	<span class="comment">//文件通道	</span></span><br><span class="line"><span class="type">ByteBuffer</span> <span class="variable">writeBuffer</span> <span class="operator">=</span> <span class="literal">null</span>;	<span class="comment">//堆外内存ByteBuffer</span></span><br><span class="line"><span class="type">TransientStorePool</span> <span class="variable">transientStorePool</span> <span class="operator">=</span> <span class="literal">null</span>;	<span class="comment">//堆外内存池</span></span><br><span class="line">String fileName;	<span class="comment">//文件名称</span></span><br><span class="line"><span class="type">long</span> fileFromOffset;	<span class="comment">//该文件的处理偏移量</span></span><br><span class="line">File file;	<span class="comment">//物理文件</span></span><br><span class="line">MappedByteBuffer mappedByteBuffer;	<span class="comment">//物理文件对应的内存映射Buffer</span></span><br><span class="line"><span class="keyword">volatile</span> <span class="type">long</span> <span class="variable">storeTimestamp</span> <span class="operator">=</span> <span class="number">0</span>;	<span class="comment">//文件最后一次内容写入时间</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">firstCreateInQueue</span> <span class="operator">=</span> <span class="literal">false</span>;	<span class="comment">//是否是MappedFileQueue队列中第一个文件</span></span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile初始化</strong></em></p>
<ul>
<li>未开启<code>transientStorePoolEnable</code>。<code>transientStorePoolEnable=true</code>为<code>true</code>表示数据先存储到堆外内存，然后通过<code>Commit</code>线程将数据提交到内存映射Buffer中，再通过<code>Flush</code>线程将内存映射<code>Buffer</code>中数据持久化磁盘。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">(<span class="keyword">final</span> String fileName, <span class="keyword">final</span> <span class="type">int</span> fileSize)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    <span class="built_in">this</span>.fileName = fileName;</span><br><span class="line">    <span class="built_in">this</span>.fileSize = fileSize;</span><br><span class="line">    <span class="built_in">this</span>.file = <span class="keyword">new</span> <span class="title class_">File</span>(fileName);</span><br><span class="line">    <span class="built_in">this</span>.fileFromOffset = Long.parseLong(<span class="built_in">this</span>.file.getName());</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">ok</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">	</span><br><span class="line">    ensureDirOK(<span class="built_in">this</span>.file.getParent());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.fileChannel = <span class="keyword">new</span> <span class="title class_">RandomAccessFile</span>(<span class="built_in">this</span>.file, <span class="string">&quot;rw&quot;</span>).getChannel();</span><br><span class="line">        <span class="built_in">this</span>.mappedByteBuffer = <span class="built_in">this</span>.fileChannel.map(MapMode.READ_WRITE, <span class="number">0</span>, fileSize);</span><br><span class="line">        TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize);</span><br><span class="line">        TOTAL_MAPPED_FILES.incrementAndGet();</span><br><span class="line">        ok = <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (FileNotFoundException e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;create file channel &quot;</span> + <span class="built_in">this</span>.fileName + <span class="string">&quot; Failed. &quot;</span>, e);</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;map file &quot;</span> + <span class="built_in">this</span>.fileName + <span class="string">&quot; Failed. &quot;</span>, e);</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!ok &amp;&amp; <span class="built_in">this</span>.fileChannel != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="built_in">this</span>.fileChannel.close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>开启<code>transientStorePoolEnable</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">(<span class="keyword">final</span> String fileName, <span class="keyword">final</span> <span class="type">int</span> fileSize,</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> TransientStorePool transientStorePool)</span> <span class="keyword">throws</span> IOException &#123;</span><br><span class="line">    init(fileName, fileSize);</span><br><span class="line">    <span class="built_in">this</span>.writeBuffer = transientStorePool.borrowBuffer();	<span class="comment">//初始化writeBuffer</span></span><br><span class="line">    <span class="built_in">this</span>.transientStorePool = transientStorePool;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile提交</strong></em></p>
<p>提交数据到FileChannel，commitLeastPages为本次提交最小的页数，如果待提交数据不满commitLeastPages，则不执行本次提交操作。如果writeBuffer如果为空，直接返回writePosition指针，无需执行commit操作，表名commit操作主体是writeBuffer。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">commit</span><span class="params">(<span class="keyword">final</span> <span class="type">int</span> commitLeastPages)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (writeBuffer == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">//no need to commit data to file channel, so just regard wrotePosition as committedPosition.</span></span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.wrotePosition.get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//判断是否满足提交条件</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.isAbleToCommit(commitLeastPages)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.hold()) &#123;</span><br><span class="line">            commit0(commitLeastPages);</span><br><span class="line">            <span class="built_in">this</span>.release();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            log.warn(<span class="string">&quot;in commit, hold failed, commit offset = &quot;</span> + <span class="built_in">this</span>.committedPosition.get());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 所有数据提交后,清空缓冲区</span></span><br><span class="line">    <span class="keyword">if</span> (writeBuffer != <span class="literal">null</span> &amp;&amp; <span class="built_in">this</span>.transientStorePool != <span class="literal">null</span> &amp;&amp; <span class="built_in">this</span>.fileSize == <span class="built_in">this</span>.committedPosition.get()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.transientStorePool.returnBuffer(writeBuffer);</span><br><span class="line">        <span class="built_in">this</span>.writeBuffer = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.committedPosition.get();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile#isAbleToCommit</strong></em></p>
<p>判断是否执行commit操作，如果文件已满返回true；如果commitLeastpages大于0，则比较writePosition与上一次提交的指针commitPosition的差值，除以OS_PAGE_SIZE得到当前脏页的数量，如果大于commitLeastPages则返回true，如果commitLeastpages小于0表示只要存在脏页就提交。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">isAbleToCommit</span><span class="params">(<span class="keyword">final</span> <span class="type">int</span> commitLeastPages)</span> &#123;</span><br><span class="line">    <span class="comment">//已经刷盘指针</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">flush</span> <span class="operator">=</span> <span class="built_in">this</span>.committedPosition.get();</span><br><span class="line">    <span class="comment">//文件写指针</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">write</span> <span class="operator">=</span> <span class="built_in">this</span>.wrotePosition.get();</span><br><span class="line">	<span class="comment">//写满刷盘</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.isFull()) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (commitLeastPages &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">//文件内容达到commitLeastPages页数,则刷盘</span></span><br><span class="line">        <span class="keyword">return</span> ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) &gt;= commitLeastPages;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> write &gt; flush;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile#commit0</strong></em></p>
<p>具体提交的实现，首先创建WriteBuffer区共享缓存区，然后将新创建的position回退到上一次提交的位置（commitPosition），设置limit为wrotePosition（当前最大有效数据指针），然后把commitPosition到wrotePosition的数据写入到FileChannel中，然后更新committedPosition指针为wrotePosition。commit的作用就是将MappedFile的writeBuffer中数据提交到文件通道FileChannel中。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">commit0</span><span class="params">(<span class="keyword">final</span> <span class="type">int</span> commitLeastPages)</span> &#123;</span><br><span class="line">    <span class="comment">//写指针</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">writePos</span> <span class="operator">=</span> <span class="built_in">this</span>.wrotePosition.get();</span><br><span class="line">    <span class="comment">//上次提交指针</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">lastCommittedPosition</span> <span class="operator">=</span> <span class="built_in">this</span>.committedPosition.get();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (writePos - <span class="built_in">this</span>.committedPosition.get() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//复制共享内存区域</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> writeBuffer.slice();</span><br><span class="line">            <span class="comment">//设置提交位置是上次提交位置</span></span><br><span class="line">            byteBuffer.position(lastCommittedPosition);</span><br><span class="line">            <span class="comment">//最大提交数量</span></span><br><span class="line">            byteBuffer.limit(writePos);</span><br><span class="line">            <span class="comment">//设置fileChannel位置为上次提交位置</span></span><br><span class="line">            <span class="built_in">this</span>.fileChannel.position(lastCommittedPosition);</span><br><span class="line">            <span class="comment">//将lastCommittedPosition到writePos的数据复制到FileChannel中</span></span><br><span class="line">            <span class="built_in">this</span>.fileChannel.write(byteBuffer);</span><br><span class="line">            <span class="comment">//重置提交位置</span></span><br><span class="line">            <span class="built_in">this</span>.committedPosition.set(writePos);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;Error occurred when commit data to FileChannel.&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile#flush</strong></em></p>
<p>刷写磁盘，直接调用MappedByteBuffer或fileChannel的force方法将内存中的数据持久化到磁盘，那么flushedPosition应该等于MappedByteBuffer中的写指针；如果writeBuffer不为空，则flushPosition应该等于上一次的commit指针；因为上一次提交的数据就是进入到MappedByteBuffer中的数据；如果writeBuffer为空，数据时直接进入到MappedByteBuffer，wrotePosition代表的是MappedByteBuffer中的指针，故设置flushPosition为wrotePosition。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/flush.jpg"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">flush</span><span class="params">(<span class="keyword">final</span> <span class="type">int</span> flushLeastPages)</span> &#123;</span><br><span class="line">    <span class="comment">//数据达到刷盘条件</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.isAbleToFlush(flushLeastPages)) &#123;</span><br><span class="line">        <span class="comment">//加锁，同步刷盘</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.hold()) &#123;</span><br><span class="line">            <span class="comment">//获得读指针</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> getReadPosition();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//数据从writeBuffer提交数据到fileChannel再刷新到磁盘</span></span><br><span class="line">                <span class="keyword">if</span> (writeBuffer != <span class="literal">null</span> || <span class="built_in">this</span>.fileChannel.position() != <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="built_in">this</span>.fileChannel.force(<span class="literal">false</span>);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">//从mmap刷新数据到磁盘</span></span><br><span class="line">                    <span class="built_in">this</span>.mappedByteBuffer.force();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;Error occurred when force data to disk.&quot;</span>, e);</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">//更新刷盘位置</span></span><br><span class="line">            <span class="built_in">this</span>.flushedPosition.set(value);</span><br><span class="line">            <span class="built_in">this</span>.release();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            log.warn(<span class="string">&quot;in flush, hold failed, flush offset = &quot;</span> + <span class="built_in">this</span>.flushedPosition.get());</span><br><span class="line">            <span class="built_in">this</span>.flushedPosition.set(getReadPosition());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.getFlushedPosition();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile#getReadPosition</strong></em></p>
<p>获取当前文件最大可读指针。如果writeBuffer为空，则直接返回当前的写指针；如果writeBuffer不为空，则返回上一次提交的指针。在MappedFile设置中,只有提交了的数据（写入到MappedByteBuffer或FileChannel中的数据）才是安全的数据</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getReadPosition</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//如果writeBuffer为空,刷盘的位置就是应该等于上次commit的位置,如果为空则为mmap的写指针</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.writeBuffer == <span class="literal">null</span> ? <span class="built_in">this</span>.wrotePosition.get() : <span class="built_in">this</span>.committedPosition.get();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile#selectMappedBuffer</strong></em></p>
<p>查找pos到当前最大可读之间的数据，由于在整个写入期间都未曾改MappedByteBuffer的指针，如果mappedByteBuffer.slice()方法返回的共享缓存区空间为整个MappedFile，然后通过设置ByteBuffer的position为待查找的值，读取字节长度当前可读最大长度，最终返回的ByteBuffer的limit为size。整个共享缓存区的容量为（MappedFile#fileSize-pos）。故在操作SelectMappedBufferResult不能对包含在里面的ByteBuffer调用filp方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> SelectMappedBufferResult <span class="title function_">selectMappedBuffer</span><span class="params">(<span class="type">int</span> pos)</span> &#123;</span><br><span class="line">    <span class="comment">//获得最大可读指针</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">readPosition</span> <span class="operator">=</span> getReadPosition();</span><br><span class="line">    <span class="comment">//pos小于最大可读指针,并且大于0</span></span><br><span class="line">    <span class="keyword">if</span> (pos &lt; readPosition &amp;&amp; pos &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.hold()) &#123;</span><br><span class="line">            <span class="comment">//复制mappedByteBuffer读共享区</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> <span class="built_in">this</span>.mappedByteBuffer.slice();</span><br><span class="line">            <span class="comment">//设置读指针位置</span></span><br><span class="line">            byteBuffer.position(pos);</span><br><span class="line">            <span class="comment">//获得可读范围</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> readPosition - pos;</span><br><span class="line">            <span class="comment">//设置最大刻度范围</span></span><br><span class="line">            <span class="type">ByteBuffer</span> <span class="variable">byteBufferNew</span> <span class="operator">=</span> byteBuffer.slice();</span><br><span class="line">            byteBufferNew.limit(size);</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SelectMappedBufferResult</span>(<span class="built_in">this</span>.fileFromOffset + pos, byteBufferNew, size, <span class="built_in">this</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>MappedFile#shutdown</strong></em></p>
<p>MappedFile文件销毁的实现方法为public boolean destory(long intervalForcibly)，intervalForcibly表示拒绝被销毁的最大存活时间。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shutdown</span><span class="params">(<span class="keyword">final</span> <span class="type">long</span> intervalForcibly)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.available) &#123;</span><br><span class="line">        <span class="comment">//关闭MapedFile</span></span><br><span class="line">        <span class="built_in">this</span>.available = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">//设置当前关闭时间戳</span></span><br><span class="line">        <span class="built_in">this</span>.firstShutdownTimestamp = System.currentTimeMillis();</span><br><span class="line">        <span class="comment">//释放资源</span></span><br><span class="line">        <span class="built_in">this</span>.release();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">this</span>.getRefCount() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((System.currentTimeMillis() - <span class="built_in">this</span>.firstShutdownTimestamp) &gt;= intervalForcibly) &#123;</span><br><span class="line">            <span class="built_in">this</span>.refCount.set(-<span class="number">1000</span> - <span class="built_in">this</span>.getRefCount());</span><br><span class="line">            <span class="built_in">this</span>.release();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="3）TransientStorePool"><a href="#3）TransientStorePool" class="headerlink" title="3）TransientStorePool"></a>3）TransientStorePool</h4><p>短暂的存储池。RocketMQ单独创建一个MappedByteBuffer内存缓存池，用来临时存储数据，数据先写入该内存映射中，然后由commit线程定时将数据从该内存复制到与目标物理文件对应的内存映射中。RocketMQ引入该机制主要的原因是提供一种内存锁定，将当前堆外内存一直锁定在内存中，避免被进程将内存交换到磁盘。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/TransientStorePool.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> poolSize;		<span class="comment">//availableBuffers个数</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">int</span> fileSize;		<span class="comment">//每隔ByteBuffer大小</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Deque&lt;ByteBuffer&gt; availableBuffers;	<span class="comment">//ByteBuffer容器。双端队列</span></span><br></pre></td></tr></table></figure>



<p><em><strong>初始化</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//创建poolSize个堆外内存</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; poolSize; i++) &#123;</span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> ByteBuffer.allocateDirect(fileSize);</span><br><span class="line">        <span class="keyword">final</span> <span class="type">long</span> <span class="variable">address</span> <span class="operator">=</span> ((DirectBuffer) byteBuffer).address();</span><br><span class="line">        <span class="type">Pointer</span> <span class="variable">pointer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Pointer</span>(address);</span><br><span class="line">        <span class="comment">//使用com.sun.jna.Library类库将该批内存锁定,避免被置换到交换区,提高存储性能</span></span><br><span class="line">        LibC.INSTANCE.mlock(pointer, <span class="keyword">new</span> <span class="title class_">NativeLong</span>(fileSize));</span><br><span class="line"></span><br><span class="line">        availableBuffers.offer(byteBuffer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-4-5-实时更新消息消费队列与索引文件"><a href="#2-4-5-实时更新消息消费队列与索引文件" class="headerlink" title="2.4.5 实时更新消息消费队列与索引文件"></a>2.4.5 实时更新消息消费队列与索引文件</h3><p>消息消费队文件、消息属性索引文件都是基于CommitLog文件构建的，当消息生产者提交的消息存储在CommitLog文件中，ConsumerQueue、IndexFile需要及时更新，否则消息无法及时被消费，根据消息属性查找消息也会出现较大延迟。RocketMQ通过开启一个线程ReputMessageService来准实时转发CommitLog文件更新事件，相应的任务处理器根据转发的消息及时更新ConsumerQueue、IndexFile文件。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84.png"
                      alt="img"
                ></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%9E%84%E5%BB%BA%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E9%98%9F%E5%88%97%E5%92%8C%E7%B4%A2%E5%BC%95%E6%96%87%E4%BB%B6.png"
                      alt="img"
                ></p>
<p><em><strong>代码：DefaultMessageStore：start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置CommitLog内存中最大偏移量</span></span><br><span class="line"><span class="built_in">this</span>.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);</span><br><span class="line"><span class="comment">//启动</span></span><br><span class="line"><span class="built_in">this</span>.reputMessageService.start();</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore：run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    DefaultMessageStore.log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service started&quot;</span>);</span><br><span class="line">	<span class="comment">//每隔1毫秒就继续尝试推送消息到消息消费队列和索引文件</span></span><br><span class="line">    <span class="keyword">while</span> (!<span class="built_in">this</span>.isStopped()) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1</span>);</span><br><span class="line">            <span class="built_in">this</span>.doReput();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            DefaultMessageStore.log.warn(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service has exception. &quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    DefaultMessageStore.log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service end&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore：deReput</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//从result中循环遍历消息,一次读一条,创建DispatherRequest对象。</span></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">readSize</span> <span class="operator">=</span> <span class="number">0</span>; readSize &lt; result.getSize() &amp;&amp; doNext; ) &#123;</span><br><span class="line">	<span class="type">DispatchRequest</span> <span class="variable">dispatchRequest</span> <span class="operator">=</span>                               DefaultMessageStore.<span class="built_in">this</span>.commitLog.checkMessageAndReturnSize(result.getByteBuffer(), <span class="literal">false</span>, <span class="literal">false</span>);</span><br><span class="line">	<span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> dispatchRequest.getBufferSize() == -<span class="number">1</span> ? dispatchRequest.getMsgSize() : dispatchRequest.getBufferSize();</span><br><span class="line"></span><br><span class="line">	<span class="keyword">if</span> (dispatchRequest.isSuccess()) &#123;</span><br><span class="line">	    <span class="keyword">if</span> (size &gt; <span class="number">0</span>) &#123;</span><br><span class="line">	        DefaultMessageStore.<span class="built_in">this</span>.doDispatch(dispatchRequest);</span><br><span class="line">	    &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>DispatchRequest</strong></em></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/DispatchRequest.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">String topic; <span class="comment">//消息主题名称</span></span><br><span class="line"><span class="type">int</span> queueId;  <span class="comment">//消息队列ID</span></span><br><span class="line"><span class="type">long</span> commitLogOffset;	<span class="comment">//消息物理偏移量</span></span><br><span class="line"><span class="type">int</span> msgSize;	<span class="comment">//消息长度</span></span><br><span class="line"><span class="type">long</span> tagsCode;	<span class="comment">//消息过滤tag hashCode</span></span><br><span class="line"><span class="type">long</span> storeTimestamp;	<span class="comment">//消息存储时间戳</span></span><br><span class="line"><span class="type">long</span> consumeQueueOffset;	<span class="comment">//消息队列偏移量</span></span><br><span class="line">String keys;	<span class="comment">//消息索引key</span></span><br><span class="line"><span class="type">boolean</span> success;	<span class="comment">//是否成功解析到完整的消息</span></span><br><span class="line">String uniqKey;	<span class="comment">//消息唯一键</span></span><br><span class="line"><span class="type">int</span> sysFlag;	<span class="comment">//消息系统标记</span></span><br><span class="line"><span class="type">long</span> preparedTransactionOffset;	<span class="comment">//消息预处理事务偏移量</span></span><br><span class="line">Map&lt;String, String&gt; propertiesMap;	<span class="comment">//消息属性</span></span><br><span class="line"><span class="type">byte</span>[] bitMap;	<span class="comment">//位图</span></span><br></pre></td></tr></table></figure>



<h4 id="1）转发到ConsumerQueue"><a href="#1）转发到ConsumerQueue" class="headerlink" title="1）转发到ConsumerQueue"></a>1）转发到ConsumerQueue</h4><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%88%86%E5%8F%91%E5%88%B0%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E9%98%9F%E5%88%97.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CommitLogDispatcherBuildConsumeQueue</span> <span class="keyword">implements</span> <span class="title class_">CommitLogDispatcher</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dispatch</span><span class="params">(DispatchRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">tranType</span> <span class="operator">=</span> MessageSysFlag.getTransactionValue(request.getSysFlag());</span><br><span class="line">        <span class="keyword">switch</span> (tranType) &#123;</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_NOT_TYPE:</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_COMMIT_TYPE:</span><br><span class="line">                <span class="comment">//消息分发</span></span><br><span class="line">                DefaultMessageStore.<span class="built_in">this</span>.putMessagePositionInfo(request);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_PREPARED_TYPE:</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#putMessagePositionInfo</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">putMessagePositionInfo</span><span class="params">(DispatchRequest dispatchRequest)</span> &#123;</span><br><span class="line">    <span class="comment">//获得消费队列</span></span><br><span class="line">    <span class="type">ConsumeQueue</span> <span class="variable">cq</span> <span class="operator">=</span> <span class="built_in">this</span>.findConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId());</span><br><span class="line">    <span class="comment">//消费队列分发消息</span></span><br><span class="line">    cq.putMessagePositionInfoWrapper(dispatchRequest);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#putMessagePositionInfo</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//依次将消息偏移量、消息长度、tag写入到ByteBuffer中</span></span><br><span class="line"><span class="built_in">this</span>.byteBufferIndex.flip();</span><br><span class="line"><span class="built_in">this</span>.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE);</span><br><span class="line"><span class="built_in">this</span>.byteBufferIndex.putLong(offset);</span><br><span class="line"><span class="built_in">this</span>.byteBufferIndex.putInt(size);</span><br><span class="line"><span class="built_in">this</span>.byteBufferIndex.putLong(tagsCode);</span><br><span class="line"><span class="comment">//获得内存映射文件</span></span><br><span class="line"><span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> <span class="built_in">this</span>.mappedFileQueue.getLastMappedFile(expectLogicOffset);</span><br><span class="line"><span class="keyword">if</span> (mappedFile != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="comment">//将消息追加到内存映射文件,异步输盘</span></span><br><span class="line">    <span class="keyword">return</span> mappedFile.appendMessage(<span class="built_in">this</span>.byteBufferIndex.array());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="2）转发到Index"><a href="#2）转发到Index" class="headerlink" title="2）转发到Index"></a>2）转发到Index</h4><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E5%88%86%E5%8F%91%E5%88%B0%E7%B4%A2%E5%BC%95%E6%96%87%E4%BB%B6.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CommitLogDispatcherBuildIndex</span> <span class="keyword">implements</span> <span class="title class_">CommitLogDispatcher</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dispatch</span><span class="params">(DispatchRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (DefaultMessageStore.<span class="built_in">this</span>.messageStoreConfig.isMessageIndexEnable()) &#123;</span><br><span class="line">            DefaultMessageStore.<span class="built_in">this</span>.indexService.buildIndex(request);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#buildIndex</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">buildIndex</span><span class="params">(DispatchRequest req)</span> &#123;</span><br><span class="line">    <span class="comment">//获得索引文件</span></span><br><span class="line">    <span class="type">IndexFile</span> <span class="variable">indexFile</span> <span class="operator">=</span> retryGetAndCreateIndexFile();</span><br><span class="line">    <span class="keyword">if</span> (indexFile != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">//获得文件最大物理偏移量</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">endPhyOffset</span> <span class="operator">=</span> indexFile.getEndPhyOffset();</span><br><span class="line">        <span class="type">DispatchRequest</span> <span class="variable">msg</span> <span class="operator">=</span> req;</span><br><span class="line">        <span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> msg.getTopic();</span><br><span class="line">        <span class="type">String</span> <span class="variable">keys</span> <span class="operator">=</span> msg.getKeys();</span><br><span class="line">        <span class="comment">//如果该消息的物理偏移量小于索引文件中的最大物理偏移量,则说明是重复数据,忽略本次索引构建</span></span><br><span class="line">        <span class="keyword">if</span> (msg.getCommitLogOffset() &lt; endPhyOffset) &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">tranType</span> <span class="operator">=</span> MessageSysFlag.getTransactionValue(msg.getSysFlag());</span><br><span class="line">        <span class="keyword">switch</span> (tranType) &#123;</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_NOT_TYPE:</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_PREPARED_TYPE:</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_COMMIT_TYPE:</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">		</span><br><span class="line">        <span class="comment">//如果消息ID不为空,则添加到Hash索引中</span></span><br><span class="line">        <span class="keyword">if</span> (req.getUniqKey() != <span class="literal">null</span>) &#123;</span><br><span class="line">            indexFile = putKey(indexFile, msg, buildKey(topic, req.getUniqKey()));</span><br><span class="line">            <span class="keyword">if</span> (indexFile == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">		<span class="comment">//构建索引key,RocketMQ支持为同一个消息建立多个索引,多个索引键空格隔开.</span></span><br><span class="line">        <span class="keyword">if</span> (keys != <span class="literal">null</span> &amp;&amp; keys.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            String[] keyset = keys.split(MessageConst.KEY_SEPARATOR);</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; keyset.length; i++) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> keyset[i];</span><br><span class="line">                <span class="keyword">if</span> (key.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    indexFile = putKey(indexFile, msg, buildKey(topic, key));</span><br><span class="line">                    <span class="keyword">if</span> (indexFile == <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">return</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        log.error(<span class="string">&quot;build index error, stop building index&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-4-6-消息队列和索引文件恢复"><a href="#2-4-6-消息队列和索引文件恢复" class="headerlink" title="2.4.6 消息队列和索引文件恢复"></a>2.4.6 消息队列和索引文件恢复</h3><p>由于RocketMQ存储首先将消息全量存储在CommitLog文件中，然后异步生成转发任务更新ConsumerQueue和Index文件。如果消息成功存储到CommitLog文件中，转发任务未成功执行，此时消息服务器Broker由于某个愿意宕机，导致CommitLog、ConsumerQueue、IndexFile文件数据不一致。如果不加以人工修复的话，会有一部分消息即便在CommitLog中文件中存在，但由于没有转发到ConsumerQueue，这部分消息将永远复发被消费者消费。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D%E6%80%BB%E4%BD%93%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p>####1）存储文件加载</p>
<p><em><strong>代码：DefaultMessageStore#load</strong></em></p>
<p>判断上一次是否异常退出。实现机制是Broker在启动时创建abort文件，在退出时通过JVM钩子函数删除abort文件。如果下次启动时存在abort文件。说明Broker时异常退出的，CommitLog与ConsumerQueue数据有可能不一致，需要进行修复。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//判断临时文件是否存在</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">lastExitOK</span> <span class="operator">=</span> !<span class="built_in">this</span>.isTempFileExist();</span><br><span class="line"><span class="comment">//根据临时文件判断当前Broker是否异常退出</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isTempFileExist</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">fileName</span> <span class="operator">=</span> StorePathConfigHelper</span><br><span class="line">        .getAbortFile(<span class="built_in">this</span>.messageStoreConfig.getStorePathRootDir());</span><br><span class="line">    <span class="type">File</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(fileName);</span><br><span class="line">    <span class="keyword">return</span> file.exists();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#load</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//加载延时队列</span></span><br><span class="line"><span class="keyword">if</span> (<span class="literal">null</span> != scheduleMessageService) &#123;</span><br><span class="line">    result = result &amp;&amp; <span class="built_in">this</span>.scheduleMessageService.load();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载CommitLog文件</span></span><br><span class="line">result = result &amp;&amp; <span class="built_in">this</span>.commitLog.load();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载消费队列文件</span></span><br><span class="line">result = result &amp;&amp; <span class="built_in">this</span>.loadConsumeQueue();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (result) &#123;</span><br><span class="line">	<span class="comment">//加载存储监测点,监测点主要记录CommitLog文件、ConsumerQueue文件、Index索引文件的刷盘点</span></span><br><span class="line">    <span class="built_in">this</span>.storeCheckpoint =<span class="keyword">new</span> <span class="title class_">StoreCheckpoint</span>(StorePathConfigHelper.getStoreCheckpoint(<span class="built_in">this</span>.messageStoreConfig.getStorePathRootDir()));</span><br><span class="line">	<span class="comment">//加载index文件</span></span><br><span class="line">    <span class="built_in">this</span>.indexService.load(lastExitOK);</span><br><span class="line">	<span class="comment">//根据Broker是否异常退出,执行不同的恢复策略</span></span><br><span class="line">    <span class="built_in">this</span>.recover(lastExitOK);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MappedFileQueue#load</strong></em></p>
<p>加载CommitLog到映射文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//指向CommitLog文件目录</span></span><br><span class="line"><span class="type">File</span> <span class="variable">dir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="built_in">this</span>.storePath);</span><br><span class="line"><span class="comment">//获得文件数组</span></span><br><span class="line">File[] files = dir.listFiles();</span><br><span class="line"><span class="keyword">if</span> (files != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="comment">// 文件排序</span></span><br><span class="line">    Arrays.sort(files);</span><br><span class="line">    <span class="comment">//遍历文件</span></span><br><span class="line">    <span class="keyword">for</span> (File file : files) &#123;</span><br><span class="line">		<span class="comment">//如果文件大小和配置文件不一致,退出</span></span><br><span class="line">        <span class="keyword">if</span> (file.length() != <span class="built_in">this</span>.mappedFileSize) &#123;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//创建映射文件</span></span><br><span class="line">            <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MappedFile</span>(file.getPath(), mappedFileSize);</span><br><span class="line">            mappedFile.setWrotePosition(<span class="built_in">this</span>.mappedFileSize);</span><br><span class="line">            mappedFile.setFlushedPosition(<span class="built_in">this</span>.mappedFileSize);</span><br><span class="line">            mappedFile.setCommittedPosition(<span class="built_in">this</span>.mappedFileSize);</span><br><span class="line">            <span class="comment">//将映射文件添加到队列</span></span><br><span class="line">            <span class="built_in">this</span>.mappedFiles.add(mappedFile);</span><br><span class="line">            log.info(<span class="string">&quot;load &quot;</span> + file.getPath() + <span class="string">&quot; OK&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;load file &quot;</span> + file + <span class="string">&quot; error&quot;</span>, e);</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#loadConsumeQueue</strong></em></p>
<p>加载消息消费队列</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//执行消费队列目录</span></span><br><span class="line"><span class="type">File</span> <span class="variable">dirLogic</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(StorePathConfigHelper.getStorePathConsumeQueue(<span class="built_in">this</span>.messageStoreConfig.getStorePathRootDir()));</span><br><span class="line"><span class="comment">//遍历消费队列目录</span></span><br><span class="line">File[] fileTopicList = dirLogic.listFiles();</span><br><span class="line"><span class="keyword">if</span> (fileTopicList != <span class="literal">null</span>) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (File fileTopic : fileTopicList) &#123;</span><br><span class="line">        <span class="comment">//获得子目录名称,即topic名称</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> fileTopic.getName();</span><br><span class="line">		<span class="comment">//遍历子目录下的消费队列文件</span></span><br><span class="line">        File[] fileQueueIdList = fileTopic.listFiles();</span><br><span class="line">        <span class="keyword">if</span> (fileQueueIdList != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//遍历文件</span></span><br><span class="line">            <span class="keyword">for</span> (File fileQueueId : fileQueueIdList) &#123;</span><br><span class="line">                <span class="comment">//文件名称即队列ID</span></span><br><span class="line">                <span class="type">int</span> queueId;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    queueId = Integer.parseInt(fileQueueId.getName());</span><br><span class="line">                &#125; <span class="keyword">catch</span> (NumberFormatException e) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="comment">//创建消费队列并加载到内存</span></span><br><span class="line">                <span class="type">ConsumeQueue</span> <span class="variable">logic</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConsumeQueue</span>(</span><br><span class="line">                    topic,</span><br><span class="line">                    queueId,</span><br><span class="line">                    StorePathConfigHelper.getStorePathConsumeQueue(<span class="built_in">this</span>.messageStoreConfig.getStorePathRootDir()),</span><br><span class="line">            <span class="built_in">this</span>.getMessageStoreConfig().getMapedFileSizeConsumeQueue(),</span><br><span class="line">                    <span class="built_in">this</span>);</span><br><span class="line">                <span class="built_in">this</span>.putConsumeQueue(topic, queueId, logic);</span><br><span class="line">                <span class="keyword">if</span> (!logic.load()) &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">log.info(<span class="string">&quot;load logics queue all over, OK&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：IndexService#load</strong></em></p>
<p>加载索引文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">load</span><span class="params">(<span class="keyword">final</span> <span class="type">boolean</span> lastExitOK)</span> &#123;</span><br><span class="line">    <span class="comment">//索引文件目录</span></span><br><span class="line">    <span class="type">File</span> <span class="variable">dir</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="built_in">this</span>.storePath);</span><br><span class="line">    <span class="comment">//遍历索引文件</span></span><br><span class="line">    File[] files = dir.listFiles();</span><br><span class="line">    <span class="keyword">if</span> (files != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">//文件排序</span></span><br><span class="line">        Arrays.sort(files);</span><br><span class="line">        <span class="comment">//遍历文件</span></span><br><span class="line">        <span class="keyword">for</span> (File file : files) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//加载索引文件</span></span><br><span class="line">                <span class="type">IndexFile</span> <span class="variable">f</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IndexFile</span>(file.getPath(), <span class="built_in">this</span>.hashSlotNum, <span class="built_in">this</span>.indexNum, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">                f.load();</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!lastExitOK) &#123;</span><br><span class="line">                    <span class="comment">//索引文件上次的刷盘时间小于该索引文件的消息时间戳,该文件将立即删除</span></span><br><span class="line">                    <span class="keyword">if</span> (f.getEndTimestamp() &gt; <span class="built_in">this</span>.defaultMessageStore.getStoreCheckpoint()</span><br><span class="line">                        .getIndexMsgTimestamp()) &#123;</span><br><span class="line">                        f.destroy(<span class="number">0</span>);</span><br><span class="line">                        <span class="keyword">continue</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">				<span class="comment">//将索引文件添加到队列</span></span><br><span class="line">                log.info(<span class="string">&quot;load index file OK, &quot;</span> + f.getFileName());</span><br><span class="line">                <span class="built_in">this</span>.indexFileList.add(f);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;load file &#123;&#125; error&quot;</span>, file, e);</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (NumberFormatException e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;load file &#123;&#125; error&quot;</span>, file, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#recover</strong></em></p>
<p>文件恢复，根据Broker是否正常退出执行不同的恢复策略</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">recover</span><span class="params">(<span class="keyword">final</span> <span class="type">boolean</span> lastExitOK)</span> &#123;</span><br><span class="line">    <span class="comment">//获得最大的物理便宜消费队列</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">maxPhyOffsetOfConsumeQueue</span> <span class="operator">=</span> <span class="built_in">this</span>.recoverConsumeQueue();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (lastExitOK) &#123;</span><br><span class="line">        <span class="comment">//正常恢复</span></span><br><span class="line">        <span class="built_in">this</span>.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//异常恢复</span></span><br><span class="line">        <span class="built_in">this</span>.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//在CommitLog中保存每个消息消费队列当前的存储逻辑偏移量</span></span><br><span class="line">    <span class="built_in">this</span>.recoverTopicQueueTable();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#recoverTopicQueueTable</strong></em></p>
<p>恢复ConsumerQueue后，将在CommitLog实例中保存每隔消息队列当前的存储逻辑偏移量，这也是消息中不仅存储主题、消息队列ID、还存储了消息队列的关键所在。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recoverTopicQueueTable</span><span class="params">()</span> &#123;</span><br><span class="line">    HashMap&lt;String<span class="comment">/* topic-queueid */</span>, Long<span class="comment">/* offset */</span>&gt; table = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, Long&gt;(<span class="number">1024</span>);</span><br><span class="line">    <span class="comment">//CommitLog最小偏移量</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">minPhyOffset</span> <span class="operator">=</span> <span class="built_in">this</span>.commitLog.getMinOffset();</span><br><span class="line">    <span class="comment">//遍历消费队列,将消费队列保存在CommitLog中</span></span><br><span class="line">    <span class="keyword">for</span> (ConcurrentMap&lt;Integer, ConsumeQueue&gt; maps : <span class="built_in">this</span>.consumeQueueTable.values()) &#123;</span><br><span class="line">        <span class="keyword">for</span> (ConsumeQueue logic : maps.values()) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> logic.getTopic() + <span class="string">&quot;-&quot;</span> + logic.getQueueId();</span><br><span class="line">            table.put(key, logic.getMaxOffsetInQueue());</span><br><span class="line">            logic.correctMinOffset(minPhyOffset);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.commitLog.setTopicQueueTable(table);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>####2）正常恢复</p>
<p><em><strong>代码：CommitLog#recoverNormally</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">recoverNormally</span><span class="params">(<span class="type">long</span> maxPhyOffsetOfConsumeQueue)</span> &#123;</span><br><span class="line">	</span><br><span class="line">    <span class="keyword">final</span> List&lt;MappedFile&gt; mappedFiles = <span class="built_in">this</span>.mappedFileQueue.getMappedFiles();</span><br><span class="line">    <span class="keyword">if</span> (!mappedFiles.isEmpty()) &#123;</span><br><span class="line">         <span class="comment">//Broker正常停止再重启时,从倒数第三个开始恢复,如果不足3个文件,则从第一个文件开始恢复。</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> mappedFiles.size() - <span class="number">3</span>;</span><br><span class="line">        <span class="keyword">if</span> (index &lt; <span class="number">0</span>)</span><br><span class="line">            index = <span class="number">0</span>;</span><br><span class="line">        <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> mappedFiles.get(index);</span><br><span class="line">        <span class="type">ByteBuffer</span> <span class="variable">byteBuffer</span> <span class="operator">=</span> mappedFile.sliceByteBuffer();</span><br><span class="line">        <span class="type">long</span> <span class="variable">processOffset</span> <span class="operator">=</span> mappedFile.getFileFromOffset();</span><br><span class="line">        <span class="comment">//代表当前已校验通过的offset</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">mappedFileOffset</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>) &#123;</span><br><span class="line">            <span class="comment">//查找消息</span></span><br><span class="line">            <span class="type">DispatchRequest</span> <span class="variable">dispatchRequest</span> <span class="operator">=</span> <span class="built_in">this</span>.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);</span><br><span class="line">            <span class="comment">//消息长度</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">size</span> <span class="operator">=</span> dispatchRequest.getMsgSize();</span><br><span class="line">           	<span class="comment">//查找结果为true,并且消息长度大于0,表示消息正确.mappedFileOffset向前移动本消息长度</span></span><br><span class="line">            <span class="keyword">if</span> (dispatchRequest.isSuccess() &amp;&amp; size &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                mappedFileOffset += size;</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">//如果查找结果为true且消息长度等于0,表示已到该文件末尾,如果还有下一个文件,则重置processOffset和MappedFileOffset重复查找下一个文件,否则跳出循环。</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (dispatchRequest.isSuccess() &amp;&amp; size == <span class="number">0</span>) &#123;</span><br><span class="line">              index++;</span><br><span class="line">              <span class="keyword">if</span> (index &gt;= mappedFiles.size()) &#123;</span><br><span class="line">                  <span class="comment">// Current branch can not happen</span></span><br><span class="line">                  <span class="keyword">break</span>;</span><br><span class="line">              &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                  <span class="comment">//取出每个文件</span></span><br><span class="line">                  mappedFile = mappedFiles.get(index);</span><br><span class="line">                  byteBuffer = mappedFile.sliceByteBuffer();</span><br><span class="line">                  processOffset = mappedFile.getFileFromOffset();</span><br><span class="line">                  mappedFileOffset = <span class="number">0</span>;</span><br><span class="line">                  </span><br><span class="line">          		&#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 查找结果为false，表明该文件未填满所有消息，跳出循环，结束循环</span></span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (!dispatchRequest.isSuccess()) &#123;</span><br><span class="line">                log.info(<span class="string">&quot;recover physics file end, &quot;</span> + mappedFile.getFileName());</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">		<span class="comment">//更新MappedFileQueue的flushedWhere和committedWhere指针</span></span><br><span class="line">        processOffset += mappedFileOffset;</span><br><span class="line">        <span class="built_in">this</span>.mappedFileQueue.setFlushedWhere(processOffset);</span><br><span class="line">        <span class="built_in">this</span>.mappedFileQueue.setCommittedWhere(processOffset);</span><br><span class="line">        <span class="comment">//删除offset之后的所有文件</span></span><br><span class="line">        <span class="built_in">this</span>.mappedFileQueue.truncateDirtyFiles(processOffset);</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line">        <span class="keyword">if</span> (maxPhyOffsetOfConsumeQueue &gt;= processOffset) &#123;</span><br><span class="line">            <span class="built_in">this</span>.defaultMessageStore.truncateDirtyLogicFiles(processOffset);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.mappedFileQueue.setFlushedWhere(<span class="number">0</span>);</span><br><span class="line">        <span class="built_in">this</span>.mappedFileQueue.setCommittedWhere(<span class="number">0</span>);</span><br><span class="line">        <span class="built_in">this</span>.defaultMessageStore.destroyLogics();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MappedFileQueue#truncateDirtyFiles</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">truncateDirtyFiles</span><span class="params">(<span class="type">long</span> offset)</span> &#123;</span><br><span class="line">    List&lt;MappedFile&gt; willRemoveFiles = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;MappedFile&gt;();</span><br><span class="line">	<span class="comment">//遍历目录下文件</span></span><br><span class="line">    <span class="keyword">for</span> (MappedFile file : <span class="built_in">this</span>.mappedFiles) &#123;</span><br><span class="line">        <span class="comment">//文件尾部的偏移量</span></span><br><span class="line">        <span class="type">long</span> <span class="variable">fileTailOffset</span> <span class="operator">=</span> file.getFileFromOffset() + <span class="built_in">this</span>.mappedFileSize;</span><br><span class="line">        <span class="comment">//文件尾部的偏移量大于offset</span></span><br><span class="line">        <span class="keyword">if</span> (fileTailOffset &gt; offset) &#123;</span><br><span class="line">            <span class="comment">//offset大于文件的起始偏移量</span></span><br><span class="line">            <span class="keyword">if</span> (offset &gt;= file.getFileFromOffset()) &#123;</span><br><span class="line">                <span class="comment">//更新wrotePosition、committedPosition、flushedPosistion</span></span><br><span class="line">                file.setWrotePosition((<span class="type">int</span>) (offset % <span class="built_in">this</span>.mappedFileSize));</span><br><span class="line">                file.setCommittedPosition((<span class="type">int</span>) (offset % <span class="built_in">this</span>.mappedFileSize));</span><br><span class="line">                file.setFlushedPosition((<span class="type">int</span>) (offset % <span class="built_in">this</span>.mappedFileSize));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">//offset小于文件的起始偏移量,说明该文件是有效文件后面创建的,释放mappedFile占用内存,删除文件</span></span><br><span class="line">                file.destroy(<span class="number">1000</span>);</span><br><span class="line">                willRemoveFiles.add(file);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.deleteExpiredFile(willRemoveFiles);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>####3）异常恢复</p>
<p>Broker异常停止文件恢复的实现为CommitLog#recoverAbnormally。异常文件恢复步骤与正常停止文件恢复流程基本相同，其主要差别有两个。首先，正常停止默认从倒数第三个文件开始进行恢复，而异常停止则需要从最后一个文件往前走，找到第一个消息存储正常的文件。其次，如果CommitLog目录没有消息文件，如果消息消费队列目录下存在文件，则需要销毁。</p>
<p><em><strong>代码：CommitLog#recoverAbnormally</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!mappedFiles.isEmpty()) &#123;</span><br><span class="line">    <span class="comment">// Looking beginning to recover from which file</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> mappedFiles.size() - <span class="number">1</span>;</span><br><span class="line">    <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">for</span> (; index &gt;= <span class="number">0</span>; index--) &#123;</span><br><span class="line">        mappedFile = mappedFiles.get(index);</span><br><span class="line">        <span class="comment">//判断消息文件是否是一个正确的文件</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.isMappedFileMatchedRecover(mappedFile)) &#123;</span><br><span class="line">            log.info(<span class="string">&quot;recover from this mapped file &quot;</span> + mappedFile.getFileName());</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//根据索引取出mappedFile文件</span></span><br><span class="line">    <span class="keyword">if</span> (index &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        index = <span class="number">0</span>;</span><br><span class="line">        mappedFile = mappedFiles.get(index);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//...验证消息的合法性,并将消息转发到消息消费队列和索引文件</span></span><br><span class="line">       </span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    <span class="comment">//未找到mappedFile,重置flushWhere、committedWhere都为0，销毁消息队列文件</span></span><br><span class="line">    <span class="built_in">this</span>.mappedFileQueue.setFlushedWhere(<span class="number">0</span>);</span><br><span class="line">    <span class="built_in">this</span>.mappedFileQueue.setCommittedWhere(<span class="number">0</span>);</span><br><span class="line">    <span class="built_in">this</span>.defaultMessageStore.destroyLogics();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-4-7-刷盘机制"><a href="#2-4-7-刷盘机制" class="headerlink" title="2.4.7 刷盘机制"></a>2.4.7 刷盘机制</h3><p>RocketMQ的存储是基于JDK NIO的内存映射机制（MappedByteBuffer）的，消息存储首先将消息追加到内存，再根据配置的刷盘策略在不同时间进行刷写磁盘。</p>
<h4 id="同步刷盘"><a href="#同步刷盘" class="headerlink" title="同步刷盘"></a>同步刷盘</h4><p>消息追加到内存后，立即将数据刷写到磁盘文件</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%90%8C%E6%AD%A5%E5%88%B7%E7%9B%98%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p><em><strong>代码：CommitLog#handleDiskFlush</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//刷盘服务</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">GroupCommitService</span> <span class="variable">service</span> <span class="operator">=</span> (GroupCommitService) <span class="built_in">this</span>.flushCommitLogService;</span><br><span class="line"><span class="keyword">if</span> (messageExt.isWaitStoreMsgOK()) &#123;</span><br><span class="line">    <span class="comment">//封装刷盘请求</span></span><br><span class="line">    <span class="type">GroupCommitRequest</span> <span class="variable">request</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GroupCommitRequest</span>(result.getWroteOffset() + result.getWroteBytes());</span><br><span class="line">    <span class="comment">//提交刷盘请求</span></span><br><span class="line">    service.putRequest(request);</span><br><span class="line">    <span class="comment">//线程阻塞5秒，等待刷盘结束</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">flushOK</span> <span class="operator">=</span> request.waitForFlush(<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());</span><br><span class="line">    <span class="keyword">if</span> (!flushOK) &#123;</span><br><span class="line">        putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>GroupCommitRequest</strong></em></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/GroupCommitRequest.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> nextOffset;	<span class="comment">//刷盘点偏移量</span></span><br><span class="line"><span class="type">CountDownLatch</span> <span class="variable">countDownLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">1</span>);	<span class="comment">//倒计树锁存器</span></span><br><span class="line"><span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">flushOK</span> <span class="operator">=</span> <span class="literal">false</span>;	<span class="comment">//刷盘结果;默认为false</span></span><br></pre></td></tr></table></figure>



<p><em><strong>代码：GroupCommitService#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    CommitLog.log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service started&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!<span class="built_in">this</span>.isStopped()) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//线程等待10ms</span></span><br><span class="line">            <span class="built_in">this</span>.waitForRunning(<span class="number">10</span>);</span><br><span class="line">            <span class="comment">//执行提交</span></span><br><span class="line">            <span class="built_in">this</span>.doCommit();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            CommitLog.log.warn(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service has exception. &quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">	...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：GroupCommitService#doCommit</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">doCommit</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//加锁</span></span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>.requestsRead) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">this</span>.requestsRead.isEmpty()) &#123;</span><br><span class="line">            <span class="comment">//遍历requestsRead</span></span><br><span class="line">            <span class="keyword">for</span> (GroupCommitRequest req : <span class="built_in">this</span>.requestsRead) &#123;</span><br><span class="line">                <span class="comment">// There may be a message in the next file, so a maximum of</span></span><br><span class="line">                <span class="comment">// two times the flush</span></span><br><span class="line">                <span class="type">boolean</span> <span class="variable">flushOK</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">2</span> &amp;&amp; !flushOK; i++) &#123;</span><br><span class="line">                    flushOK = CommitLog.<span class="built_in">this</span>.mappedFileQueue.getFlushedWhere() &gt;= req.getNextOffset();</span><br><span class="line">					<span class="comment">//刷盘</span></span><br><span class="line">                    <span class="keyword">if</span> (!flushOK) &#123;</span><br><span class="line">                        CommitLog.<span class="built_in">this</span>.mappedFileQueue.flush(<span class="number">0</span>);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">				<span class="comment">//唤醒发送消息客户端</span></span><br><span class="line">                req.wakeupCustomer(flushOK);</span><br><span class="line">            &#125;</span><br><span class="line">			</span><br><span class="line">            <span class="comment">//更新刷盘监测点</span></span><br><span class="line">            <span class="type">long</span> <span class="variable">storeTimestamp</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.mappedFileQueue.getStoreTimestamp();</span><br><span class="line">            <span class="keyword">if</span> (storeTimestamp &gt; <span class="number">0</span>) &#123;               CommitLog.<span class="built_in">this</span>.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);</span><br><span class="line">            &#125;</span><br><span class="line">			</span><br><span class="line">            <span class="built_in">this</span>.requestsRead.clear();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Because of individual messages is set to not sync flush, it</span></span><br><span class="line">            <span class="comment">// will come to this process</span></span><br><span class="line">            CommitLog.<span class="built_in">this</span>.mappedFileQueue.flush(<span class="number">0</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h4 id="异步刷盘"><a href="#异步刷盘" class="headerlink" title="异步刷盘"></a>异步刷盘</h4><p>在消息追加到内存后，立即返回给消息发送端。如果开启transientStorePoolEnable，RocketMQ会单独申请一个与目标物理文件（commitLog）同样大小的堆外内存，该堆外内存将使用内存锁定，确保不会被置换到虚拟内存中去，消息首先追加到堆外内存，然后提交到物理文件的内存映射中，然后刷写到磁盘。如果未开启transientStorePoolEnable，消息直接追加到物理文件直接映射文件中，然后刷写到磁盘中。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E5%BC%82%E6%AD%A5%E5%88%B7%E7%9B%98%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p>开启transientStorePoolEnable后异步刷盘步骤:</p>
<ol>
<li>将消息直接追加到ByteBuffer（堆外内存）</li>
<li>CommitRealTimeService线程每隔200ms将ByteBuffer新追加内容提交到MappedByteBuffer中</li>
<li>MappedByteBuffer在内存中追加提交的内容，wrotePosition指针向后移动</li>
<li>commit操作成功返回，将committedPosition位置恢复</li>
<li>FlushRealTimeService线程默认每500ms将MappedByteBuffer中新追加的内存刷写到磁盘</li>
</ol>
<p><em><strong>代码：CommitLog$CommitRealTimeService#run</strong></em></p>
<p>提交线程工作机制</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//间隔时间,默认200ms</span></span><br><span class="line"><span class="type">int</span> <span class="variable">interval</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getCommitIntervalCommitLog();</span><br><span class="line"></span><br><span class="line"><span class="comment">//一次提交的至少页数</span></span><br><span class="line"><span class="type">int</span> <span class="variable">commitDataLeastPages</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogLeastPages();</span><br><span class="line"></span><br><span class="line"><span class="comment">//两次真实提交的最大间隔,默认200ms</span></span><br><span class="line"><span class="type">int</span> <span class="variable">commitDataThoroughInterval</span> <span class="operator">=</span></span><br><span class="line">CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getCommitCommitLogThoroughInterval();</span><br><span class="line"></span><br><span class="line"><span class="comment">//上次提交间隔超过commitDataThoroughInterval,则忽略提交commitDataThoroughInterval参数,直接提交</span></span><br><span class="line"><span class="type">long</span> <span class="variable">begin</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="keyword">if</span> (begin &gt;= (<span class="built_in">this</span>.lastCommitTimestamp + commitDataThoroughInterval)) &#123;</span><br><span class="line">    <span class="built_in">this</span>.lastCommitTimestamp = begin;</span><br><span class="line">    commitDataLeastPages = <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//执行提交操作,将待提交数据提交到物理文件的内存映射区</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.mappedFileQueue.commit(commitDataLeastPages);</span><br><span class="line"><span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="keyword">if</span> (!result) &#123;</span><br><span class="line">    <span class="built_in">this</span>.lastCommitTimestamp = end; <span class="comment">// result = false means some data committed.</span></span><br><span class="line">    <span class="comment">//now wake up flush thread.</span></span><br><span class="line">    <span class="comment">//唤醒刷盘线程</span></span><br><span class="line">    flushCommitLogService.wakeup();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (end - begin &gt; <span class="number">500</span>) &#123;</span><br><span class="line">    log.info(<span class="string">&quot;Commit data to file costs &#123;&#125; ms&quot;</span>, end - begin);</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">this</span>.waitForRunning(interval);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：CommitLog$FlushRealTimeService#run</strong></em></p>
<p>刷盘线程工作机制</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//表示await方法等待,默认false</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">flushCommitLogTimed</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();</span><br><span class="line"><span class="comment">//线程执行时间间隔</span></span><br><span class="line"><span class="type">int</span> <span class="variable">interval</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();</span><br><span class="line"><span class="comment">//一次刷写任务至少包含页数</span></span><br><span class="line"><span class="type">int</span> <span class="variable">flushPhysicQueueLeastPages</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();</span><br><span class="line"><span class="comment">//两次真实刷写任务最大间隔</span></span><br><span class="line"><span class="type">int</span> <span class="variable">flushPhysicQueueThoroughInterval</span> <span class="operator">=</span></span><br><span class="line">CommitLog.<span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();</span><br><span class="line">...</span><br><span class="line"><span class="comment">//距离上次提交间隔超过flushPhysicQueueThoroughInterval,则本次刷盘任务将忽略flushPhysicQueueLeastPages,直接提交</span></span><br><span class="line"><span class="type">long</span> <span class="variable">currentTimeMillis</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="keyword">if</span> (currentTimeMillis &gt;= (<span class="built_in">this</span>.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) &#123;</span><br><span class="line">    <span class="built_in">this</span>.lastFlushTimestamp = currentTimeMillis;</span><br><span class="line">    flushPhysicQueueLeastPages = <span class="number">0</span>;</span><br><span class="line">    printFlushProgress = (printTimes++ % <span class="number">10</span>) == <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="comment">//执行一次刷盘前,先等待指定时间间隔</span></span><br><span class="line"><span class="keyword">if</span> (flushCommitLogTimed) &#123;</span><br><span class="line">    Thread.sleep(interval);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.waitForRunning(interval);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="type">long</span> <span class="variable">begin</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="comment">//刷写磁盘</span></span><br><span class="line">CommitLog.<span class="built_in">this</span>.mappedFileQueue.flush(flushPhysicQueueLeastPages);</span><br><span class="line"><span class="type">long</span> <span class="variable">storeTimestamp</span> <span class="operator">=</span> CommitLog.<span class="built_in">this</span>.mappedFileQueue.getStoreTimestamp();</span><br><span class="line"><span class="keyword">if</span> (storeTimestamp &gt; <span class="number">0</span>) &#123;</span><br><span class="line"><span class="comment">//更新存储监测点文件的时间戳</span></span><br><span class="line">CommitLog.<span class="built_in">this</span>.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);</span><br></pre></td></tr></table></figure>



<h3 id="2-4-8-过期文件删除机制"><a href="#2-4-8-过期文件删除机制" class="headerlink" title="2.4.8 过期文件删除机制"></a>2.4.8 过期文件删除机制</h3><p>由于RocketMQ操作CommitLog、ConsumerQueue文件是基于内存映射机制并在启动的时候回加载CommitLog、ConsumerQueue目录下的所有文件，为了避免内存与磁盘的浪费，不可能将消息永久存储在消息服务器上，所以要引入一种机制来删除已过期的文件。RocketMQ顺序写CommitLog、ConsumerQueue文件，所有写操作全部落在最后一个CommitLog或者ConsumerQueue文件上，之前的文件在下一个文件创建后将不会再被更新。RocketMQ清除过期文件的方法时：如果当前文件在在一定时间间隔内没有再次被消费，则认为是过期文件，可以被删除，RocketMQ不会关注这个文件上的消息是否全部被消费。默认每个文件的过期时间为72小时，通过在Broker配置文件中设置fileReservedTime来改变过期时间，单位为小时。</p>
<p><em><strong>代码：DefaultMessageStore#addScheduleTask</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">addScheduleTask</span><span class="params">()</span> &#123;</span><br><span class="line">	<span class="comment">//每隔10s调度一次清除文件</span></span><br><span class="line">    <span class="built_in">this</span>.scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            DefaultMessageStore.<span class="built_in">this</span>.cleanFilesPeriodically();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, <span class="number">1000</span> * <span class="number">60</span>, <span class="built_in">this</span>.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS);</span><br><span class="line">	...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#cleanFilesPeriodically</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">cleanFilesPeriodically</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//清除存储文件</span></span><br><span class="line">    <span class="built_in">this</span>.cleanCommitLogService.run();</span><br><span class="line">    <span class="comment">//清除消息消费队列文件</span></span><br><span class="line">    <span class="built_in">this</span>.cleanConsumeQueueService.run();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#deleteExpiredFiles</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">deleteExpiredFiles</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//删除的数量</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">deleteCount</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">    <span class="comment">//文件保留的时间</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">fileReservedTime</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.getMessageStoreConfig().getFileReservedTime();</span><br><span class="line">    <span class="comment">//删除物理文件的间隔</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">deletePhysicFilesInterval</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.getMessageStoreConfig().getDeleteCommitLogFilesInterval();</span><br><span class="line">    <span class="comment">//线程被占用,第一次拒绝删除后能保留的最大时间,超过该时间,文件将被强制删除</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">destroyMapedFileIntervalForcibly</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.getMessageStoreConfig().getDestroyMapedFileIntervalForcibly();</span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="variable">timeup</span> <span class="operator">=</span> <span class="built_in">this</span>.isTimeToDelete();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">spacefull</span> <span class="operator">=</span> <span class="built_in">this</span>.isSpaceToDelete();</span><br><span class="line"><span class="type">boolean</span> <span class="variable">manualDelete</span> <span class="operator">=</span> <span class="built_in">this</span>.manualDeleteFileSeveralTimes &gt; <span class="number">0</span>;</span><br><span class="line"><span class="keyword">if</span> (timeup || spacefull || manualDelete) &#123;</span><br><span class="line">	...执行删除逻辑</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    ...无作为</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>删除文件操作的条件</p>
<ol>
<li>指定删除文件的时间点，RocketMQ通过deleteWhen设置一天的固定时间执行一次删除过期文件操作，默认4点</li>
<li>磁盘空间如果不充足，删除过期文件</li>
<li>预留，手工触发。</li>
</ol>
<p><em><strong>代码：CleanCommitLogService#isSpaceToDelete</strong></em></p>
<p>当磁盘空间不足时执行删除过期文件</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isSpaceToDelete</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//磁盘分区的最大使用量</span></span><br><span class="line">    <span class="type">double</span> <span class="variable">ratio</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.getMessageStoreConfig().getDiskMaxUsedSpaceRatio() / <span class="number">100.0</span>;</span><br><span class="line">	<span class="comment">//是否需要立即执行删除过期文件操作</span></span><br><span class="line">    cleanImmediately = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">storePathPhysic</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.getMessageStoreConfig().getStorePathCommitLog();</span><br><span class="line">        <span class="comment">//当前CommitLog目录所在的磁盘分区的磁盘使用率</span></span><br><span class="line">        <span class="type">double</span> <span class="variable">physicRatio</span> <span class="operator">=</span> UtilAll.getDiskPartitionSpaceUsedPercent(storePathPhysic);</span><br><span class="line">        <span class="comment">//diskSpaceWarningLevelRatio:磁盘使用率警告阈值,默认0.90</span></span><br><span class="line">        <span class="keyword">if</span> (physicRatio &gt; diskSpaceWarningLevelRatio) &#123;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">diskok</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.runningFlags.getAndMakeDiskFull();</span><br><span class="line">            <span class="keyword">if</span> (diskok) &#123;</span><br><span class="line">                DefaultMessageStore.log.error(<span class="string">&quot;physic disk maybe full soon &quot;</span> + physicRatio + <span class="string">&quot;, so mark disk full&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">//diskSpaceCleanForciblyRatio:强制清除阈值,默认0.85</span></span><br><span class="line">            cleanImmediately = <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (physicRatio &gt; diskSpaceCleanForciblyRatio) &#123;</span><br><span class="line">            cleanImmediately = <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">diskok</span> <span class="operator">=</span> DefaultMessageStore.<span class="built_in">this</span>.runningFlags.getAndMakeDiskOK();</span><br><span class="line">            <span class="keyword">if</span> (!diskok) &#123;</span><br><span class="line">            DefaultMessageStore.log.info(<span class="string">&quot;physic disk space OK &quot;</span> + physicRatio + <span class="string">&quot;, so mark disk ok&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (physicRatio &lt; <span class="number">0</span> || physicRatio &gt; ratio) &#123;</span><br><span class="line">        DefaultMessageStore.log.info(<span class="string">&quot;physic disk maybe full soon, so reclaim space, &quot;</span> + physicRatio);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MappedFileQueue#deleteExpiredFileByTime</strong></em></p>
<p>执行文件销毁和删除</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; mfsLength; i++) &#123;</span><br><span class="line">    <span class="comment">//遍历每隔文件</span></span><br><span class="line">    <span class="type">MappedFile</span> <span class="variable">mappedFile</span> <span class="operator">=</span> (MappedFile) mfs[i];</span><br><span class="line">    <span class="comment">//计算文件存活时间</span></span><br><span class="line">    <span class="type">long</span> <span class="variable">liveMaxTimestamp</span> <span class="operator">=</span> mappedFile.getLastModifiedTimestamp() + expiredTime;</span><br><span class="line">    <span class="comment">//如果超过72小时,执行文件删除</span></span><br><span class="line">    <span class="keyword">if</span> (System.currentTimeMillis() &gt;= liveMaxTimestamp || cleanImmediately) &#123;</span><br><span class="line">        <span class="keyword">if</span> (mappedFile.destroy(intervalForcibly)) &#123;</span><br><span class="line">            files.add(mappedFile);</span><br><span class="line">            deleteCount++;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (files.size() &gt;= DELETE_FILES_BATCH_MAX) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (deleteFilesInterval &gt; <span class="number">0</span> &amp;&amp; (i + <span class="number">1</span>) &lt; mfsLength) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(deleteFilesInterval);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">//avoid deleting files in the middle</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>




<h3 id="2-4-9-小结"><a href="#2-4-9-小结" class="headerlink" title="2.4.9 小结"></a>2.4.9 小结</h3><p>RocketMQ的存储文件包括消息文件（Commitlog）、消息消费队列文件（ConsumerQueue）、Hash索引文件（IndexFile）、监测点文件（checkPoint）、abort（关闭异常文件）。单个消息存储文件、消息消费队列文件、Hash索引文件长度固定以便使用内存映射机制进行文件的读写操作。RocketMQ组织文件以文件的起始偏移量来命令文件，这样根据偏移量能快速定位到真实的物理文件。RocketMQ基于内存映射文件机制提供了同步刷盘和异步刷盘两种机制，异步刷盘是指在消息存储时先追加到内存映射文件，然后启动专门的刷盘线程定时将内存中的文件数据刷写到磁盘。</p>
<p>CommitLog，消息存储文件，RocketMQ为了保证消息发送的高吞吐量，采用单一文件存储所有主题消息，保证消息存储是完全的顺序写，但这样给文件读取带来了不便，为此RocketMQ为了方便消息消费构建了消息消费队列文件，基于主题与队列进行组织，同时RocketMQ为消息实现了Hash索引，可以为消息设置索引键，根据所以能够快速从CommitLog文件中检索消息。</p>
<p>当消息达到CommitLog后，会通过ReputMessageService线程接近实时地将消息转发给消息消费队列文件与索引文件。为了安全起见，RocketMQ引入abort文件，记录Broker的停机是否是正常关闭还是异常关闭，在重启Broker时为了保证CommitLog文件，消息消费队列文件与Hash索引文件的正确性，分别采用不同策略来恢复文件。</p>
<p>RocketMQ不会永久存储消息文件、消息消费队列文件，而是启动文件过期机制并在磁盘空间不足或者默认凌晨4点删除过期文件，文件保存72小时并且在删除文件时并不会判断该消息文件上的消息是否被消费。</p>
<h2 id="2-5-Consumer"><a href="#2-5-Consumer" class="headerlink" title="2.5 Consumer"></a>2.5 Consumer</h2><h3 id="2-5-1-消息消费概述"><a href="#2-5-1-消息消费概述" class="headerlink" title="2.5.1 消息消费概述"></a>2.5.1 消息消费概述</h3><p>消息消费以组的模式开展，一个消费组内可以包含多个消费者，每一个消费者组可订阅多个主题，消费组之间有ff式和广播模式两种消费模式。集群模式，主题下的同一条消息只允许被其中一个消费者消费。广播模式，主题下的同一条消息，将被集群内的所有消费者消费一次。消息服务器与消费者之间的消息传递也有两种模式：推模式、拉模式。所谓的拉模式，是消费端主动拉起拉消息请求，而推模式是消息达到消息服务器后，推送给消息消费者。RocketMQ消息推模式的实现基于拉模式，在拉模式上包装一层，一个拉取任务完成后开始下一个拉取任务。</p>
<p>集群模式下，多个消费者如何对消息队列进行负载呢？消息队列负载机制遵循一个通用思想：一个消息队列同一个时间只允许被一个消费者消费，一个消费者可以消费多个消息队列。</p>
<p>RocketMQ支持局部顺序消息消费，也就是保证同一个消息队列上的消息顺序消费。不支持消息全局顺序消费，如果要实现某一个主题的全局顺序消费，可以将该主题的队列数设置为1，牺牲高可用性。</p>
<p>###2.5.2 消息消费初探</p>
<p><strong>消息推送模式</strong></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81.png"
                      alt="img"
                ></p>
<p><strong>消息消费重要方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">sendMessageBack</span><span class="params">(<span class="keyword">final</span> MessageExt msg, <span class="keyword">final</span> <span class="type">int</span> delayLevel, <span class="keyword">final</span> String brokerName)</span>：发送消息确认</span><br><span class="line">Set&lt;MessageQueue&gt; <span class="title function_">fetchSubscribeMessageQueues</span><span class="params">(<span class="keyword">final</span> String topic)</span> :获取消费者对主题分配了那些消息队列</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">registerMessageListener</span><span class="params">(<span class="keyword">final</span> MessageListenerConcurrently messageListener)</span>：注册并发事件监听器</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">registerMessageListener</span><span class="params">(<span class="keyword">final</span> MessageListenerOrderly messageListener)</span>：注册顺序消息事件监听器</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">subscribe</span><span class="params">(<span class="keyword">final</span> String topic, <span class="keyword">final</span> String subExpression)</span>：基于主题订阅消息，消息过滤使用表达式</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">subscribe</span><span class="params">(<span class="keyword">final</span> String topic, <span class="keyword">final</span> String fullClassName,<span class="keyword">final</span> String filterClassSource)</span>：基于主题订阅消息，消息过滤使用类模式</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">subscribe</span><span class="params">(<span class="keyword">final</span> String topic, <span class="keyword">final</span> MessageSelector selector)</span> ：订阅消息，并指定队列选择器</span><br><span class="line"><span class="keyword">void</span> <span class="title function_">unsubscribe</span><span class="params">(<span class="keyword">final</span> String topic)</span>：取消消息订阅</span><br></pre></td></tr></table></figure>



<p><strong>DefaultMQPushConsumer</strong></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/DefaultMQPushConsumer.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//消费者组</span></span><br><span class="line"><span class="keyword">private</span> String consumerGroup;	</span><br><span class="line"><span class="comment">//消息消费模式</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">MessageModel</span> <span class="variable">messageModel</span> <span class="operator">=</span> MessageModel.CLUSTERING;	</span><br><span class="line"><span class="comment">//指定消费开始偏移量（最大偏移量、最小偏移量、启动时间戳）开始消费</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">ConsumeFromWhere</span> <span class="variable">consumeFromWhere</span> <span class="operator">=</span> ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;</span><br><span class="line"><span class="comment">//集群模式下的消息队列负载策略</span></span><br><span class="line"><span class="keyword">private</span> AllocateMessageQueueStrategy allocateMessageQueueStrategy;</span><br><span class="line"><span class="comment">//订阅信息</span></span><br><span class="line"><span class="keyword">private</span> Map&lt;String <span class="comment">/* topic */</span>, String <span class="comment">/* sub expression */</span>&gt; subscription = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;();</span><br><span class="line"><span class="comment">//消息业务监听器</span></span><br><span class="line"><span class="keyword">private</span> MessageListener messageListener;</span><br><span class="line"><span class="comment">//消息消费进度存储器</span></span><br><span class="line"><span class="keyword">private</span> OffsetStore offsetStore;</span><br><span class="line"><span class="comment">//消费者最小线程数量</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">consumeThreadMin</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line"><span class="comment">//消费者最大线程数量</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">consumeThreadMax</span> <span class="operator">=</span> <span class="number">20</span>;</span><br><span class="line"><span class="comment">//并发消息消费时处理队列最大跨度</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">consumeConcurrentlyMaxSpan</span> <span class="operator">=</span> <span class="number">2000</span>;</span><br><span class="line"><span class="comment">//每1000次流控后打印流控日志</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">pullThresholdForQueue</span> <span class="operator">=</span> <span class="number">1000</span>;</span><br><span class="line"><span class="comment">//推模式下任务间隔时间</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">long</span> <span class="variable">pullInterval</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"><span class="comment">//推模式下任务拉取的条数,默认32条</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">pullBatchSize</span> <span class="operator">=</span> <span class="number">32</span>;</span><br><span class="line"><span class="comment">//每次传入MessageListener#consumerMessage中消息的数量</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">consumeMessageBatchMaxSize</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="comment">//是否每次拉取消息都订阅消息</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">postSubscriptionWhenPull</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="comment">//消息重试次数,-1代表16次</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">int</span> <span class="variable">maxReconsumeTimes</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line"><span class="comment">//消息消费超时时间</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">long</span> <span class="variable">consumeTimeout</span> <span class="operator">=</span> <span class="number">15</span>;</span><br></pre></td></tr></table></figure>



<h3 id="2-5-3-消费者启动流程"><a href="#2-5-3-消费者启动流程" class="headerlink" title="2.5.3 消费者启动流程"></a>2.5.3 消费者启动流程</h3><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p><em><strong>代码：DefaultMQPushConsumerImpl#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> <span class="keyword">throws</span> MQClientException &#123;</span><br><span class="line">    <span class="keyword">switch</span> (<span class="built_in">this</span>.serviceState) &#123;</span><br><span class="line">        <span class="keyword">case</span> CREATE_JUST:</span><br><span class="line">            </span><br><span class="line">                <span class="built_in">this</span>.defaultMQPushConsumer.getMessageModel(), <span class="built_in">this</span>.defaultMQPushConsumer.isUnitMode());</span><br><span class="line">            <span class="built_in">this</span>.serviceState = ServiceState.START_FAILED;</span><br><span class="line">			<span class="comment">//检查消息者是否合法</span></span><br><span class="line">            <span class="built_in">this</span>.checkConfig();</span><br><span class="line">			<span class="comment">//构建主题订阅信息</span></span><br><span class="line">            <span class="built_in">this</span>.copySubscription();</span><br><span class="line">			<span class="comment">//设置消费者客户端实例名称为进程ID</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">this</span>.defaultMQPushConsumer.getMessageModel() == MessageModel.CLUSTERING) &#123;</span><br><span class="line">                <span class="built_in">this</span>.defaultMQPushConsumer.changeInstanceNameToPID();</span><br><span class="line">            &#125;</span><br><span class="line">			<span class="comment">//创建MQClient实例</span></span><br><span class="line">            <span class="built_in">this</span>.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(<span class="built_in">this</span>.defaultMQPushConsumer, <span class="built_in">this</span>.rpcHook);</span><br><span class="line">			<span class="comment">//构建rebalanceImpl</span></span><br><span class="line">            <span class="built_in">this</span>.rebalanceImpl.setConsumerGroup(<span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup());</span><br><span class="line">            <span class="built_in">this</span>.rebalanceImpl.setMessageModel(<span class="built_in">this</span>.defaultMQPushConsumer.getMessageModel());</span><br><span class="line">            <span class="built_in">this</span>.rebalanceImpl.setAllocateMessageQueueStrategy(<span class="built_in">this</span>.defaultMQPushConsumer.getAllocateMessageQueueStrategy());</span><br><span class="line">            <span class="built_in">this</span>.rebalanceImpl.setmQClientFactory(<span class="built_in">this</span>.mQClientFactor</span><br><span class="line">            <span class="built_in">this</span>.pullAPIWrapper = <span class="keyword">new</span> <span class="title class_">PullAPIWrapper</span>(</span><br><span class="line">                mQClientFactory,</span><br><span class="line">                <span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup(), isUnitMode());</span><br><span class="line">            <span class="built_in">this</span>.pullAPIWrapper.registerFilterMessageHook(filterMessageHookLis</span><br><span class="line">            <span class="title function_">if</span> <span class="params">(<span class="built_in">this</span>.defaultMQPushConsumer.getOffsetStore()</span> != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="built_in">this</span>.offsetStore = <span class="built_in">this</span>.defaultMQPushConsumer.getOffsetStore();</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">           		<span class="keyword">switch</span> (<span class="built_in">this</span>.defaultMQPushConsumer.getMessageModel()) &#123;</span><br><span class="line">               </span><br><span class="line">           	    <span class="keyword">case</span> BROADCASTING:	 <span class="comment">//消息消费广播模式,将消费进度保存在本地</span></span><br><span class="line">           	        <span class="built_in">this</span>.offsetStore = <span class="keyword">new</span> <span class="title class_">LocalFileOffsetStore</span>(<span class="built_in">this</span>.mQClientFactory, <span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup());</span><br><span class="line">           	            <span class="keyword">break</span>;</span><br><span class="line">           	        <span class="keyword">case</span> CLUSTERING:	<span class="comment">//消息消费集群模式,将消费进度保存在远端Broker</span></span><br><span class="line">           	            <span class="built_in">this</span>.offsetStore = <span class="keyword">new</span> <span class="title class_">RemoteBrokerOffsetStore</span>(<span class="built_in">this</span>.mQClientFactory, <span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup());</span><br><span class="line">           	            <span class="keyword">break</span>;</span><br><span class="line">           	        <span class="keyword">default</span>:</span><br><span class="line">           	            <span class="keyword">break</span>;</span><br><span class="line">           	    &#125;</span><br><span class="line">           	    <span class="built_in">this</span>.defaultMQPushConsumer.setOffsetStore(<span class="built_in">this</span>.offsetStore);</span><br><span class="line">           	&#125;</span><br><span class="line">            <span class="built_in">this</span>.offsetStore.load</span><br><span class="line">            <span class="comment">//创建顺序消息消费服务</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">this</span>.getMessageListenerInner() <span class="keyword">instanceof</span> MessageListenerOrderly) &#123;</span><br><span class="line">                <span class="built_in">this</span>.consumeOrderly = <span class="literal">true</span>;</span><br><span class="line">                <span class="built_in">this</span>.consumeMessageService =</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">ConsumeMessageOrderlyService</span>(<span class="built_in">this</span>, (MessageListenerOrderly) <span class="built_in">this</span>.getMessageListenerInner());</span><br><span class="line">                <span class="comment">//创建并发消息消费服务</span></span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">this</span>.getMessageListenerInner() <span class="keyword">instanceof</span> MessageListenerConcurrently) &#123;</span><br><span class="line">                <span class="built_in">this</span>.consumeOrderly = <span class="literal">false</span>;</span><br><span class="line">                <span class="built_in">this</span>.consumeMessageService =</span><br><span class="line">                    <span class="keyword">new</span> <span class="title class_">ConsumeMessageConcurrentlyService</span>(<span class="built_in">this</span>, (MessageListenerConcurrently) <span class="built_in">this</span>.getMessageListenerInner());</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//消息消费服务启动</span></span><br><span class="line">            <span class="built_in">this</span>.consumeMessageService.start();</span><br><span class="line">            <span class="comment">//注册消费者实例</span></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">registerOK</span> <span class="operator">=</span> mQClientFactory.registerConsumer(<span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup(), <span class="built_in">this</span>);</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> (!registerOK) &#123;</span><br><span class="line">                <span class="built_in">this</span>.serviceState = ServiceState.CREATE_JUST;</span><br><span class="line">                <span class="built_in">this</span>.consumeMessageService.shutdown();</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(<span class="string">&quot;The consumer group[&quot;</span> + <span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup()</span><br><span class="line">                    + <span class="string">&quot;] has been created before, specify another name please.&quot;</span> + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),</span><br><span class="line">                    <span class="literal">null</span>);</span><br><span class="line">            <span class="comment">//启动消费者客户端</span></span><br><span class="line">            mQClientFactory.start();</span><br><span class="line">            log.info(<span class="string">&quot;the consumer [&#123;&#125;] start OK.&quot;</span>, <span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup());</span><br><span class="line">            <span class="built_in">this</span>.serviceState = ServiceState.RUNNING;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> RUNNING:</span><br><span class="line">            <span class="keyword">case</span> START_FAILED:</span><br><span class="line">        <span class="keyword">case</span> SHUTDOWN_ALREADY:</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQClientException</span>(<span class="string">&quot;The PushConsumer service state not OK, maybe started once, &quot;</span></span><br><span class="line">                + <span class="built_in">this</span>.serviceState</span><br><span class="line">                + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),</span><br><span class="line">                <span class="literal">null</span>);</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.updateTopicSubscribeInfoWhenSubscriptionChanged();</span><br><span class="line">    <span class="built_in">this</span>.mQClientFactory.checkClientInBroker();</span><br><span class="line">    <span class="built_in">this</span>.mQClientFactory.sendHeartbeatToAllBrokerWithLock();</span><br><span class="line">    <span class="built_in">this</span>.mQClientFactory.rebalanceImmediately();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-5-4-消息拉取"><a href="#2-5-4-消息拉取" class="headerlink" title="2.5.4 消息拉取"></a>2.5.4 消息拉取</h3><p>消息消费模式有两种模式：广播模式与集群模式。广播模式比较简单，每一个消费者需要拉取订阅主题下所有队列的消息。本文重点讲解集群模式。在集群模式下，同一个消费者组内有多个消息消费者，同一个主题存在多个消费队列，消费者通过负载均衡的方式消费消息。</p>
<p>消息队列负载均衡，通常的作法是一个消息队列在同一个时间只允许被一个消费消费者消费，一个消息消费者可以同时消费多个消息队列。</p>
<h4 id="1）PullMessageService实现机制"><a href="#1）PullMessageService实现机制" class="headerlink" title="1）PullMessageService实现机制"></a>1）PullMessageService实现机制</h4><p>从MQClientInstance的启动流程中可以看出，RocketMQ使用一个单独的线程PullMessageService来负责消息的拉取。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/pullMessageService%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6.png"
                      alt="img"
                ></p>
<p><em><strong>代码：PullMessageService#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service started&quot;</span>);</span><br><span class="line">	<span class="comment">//循环拉取消息</span></span><br><span class="line">    <span class="keyword">while</span> (!<span class="built_in">this</span>.isStopped()) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//从请求队列中获取拉取消息请求</span></span><br><span class="line">            <span class="type">PullRequest</span> <span class="variable">pullRequest</span> <span class="operator">=</span> <span class="built_in">this</span>.pullRequestQueue.take();</span><br><span class="line">            <span class="comment">//拉取消息</span></span><br><span class="line">            <span class="built_in">this</span>.pullMessage(pullRequest);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException ignored) &#123;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;Pull Message Service Run Method exception&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service end&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>PullRequest</strong></p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/PullRequest.png"
                      alt="img"
                ></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> String consumerGroup;	<span class="comment">//消费者组</span></span><br><span class="line"><span class="keyword">private</span> MessageQueue messageQueue;	<span class="comment">//待拉取消息队列</span></span><br><span class="line"><span class="keyword">private</span> ProcessQueue processQueue;	<span class="comment">//消息处理队列</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">long</span> nextOffset;	<span class="comment">//待拉取的MessageQueue偏移量</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">lockedFirst</span> <span class="operator">=</span> <span class="literal">false</span>;	<span class="comment">//是否被锁定</span></span><br></pre></td></tr></table></figure>



<p><em><strong>代码：PullMessageService#pullMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">pullMessage</span><span class="params">(<span class="keyword">final</span> PullRequest pullRequest)</span> &#123;</span><br><span class="line">    <span class="comment">//获得消费者实例</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">MQConsumerInner</span> <span class="variable">consumer</span> <span class="operator">=</span> <span class="built_in">this</span>.mQClientFactory.selectConsumer(pullRequest.getConsumerGroup());</span><br><span class="line">    <span class="keyword">if</span> (consumer != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">//强转为推送模式消费者</span></span><br><span class="line">        <span class="type">DefaultMQPushConsumerImpl</span> <span class="variable">impl</span> <span class="operator">=</span> (DefaultMQPushConsumerImpl) consumer;</span><br><span class="line">        <span class="comment">//推送消息</span></span><br><span class="line">        impl.pullMessage(pullRequest);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;No matched consumer for the PullRequest &#123;&#125;, drop it&quot;</span>, pullRequest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>####2）ProcessQueue实现机制</p>
<p>ProcessQueue是MessageQueue在消费端的重现、快照。PullMessageService从消息服务器默认每次拉取32条消息，按照消息的队列偏移量顺序存放在ProcessQueue中，PullMessageService然后将消息提交到消费者消费线程池，消息成功消费后从ProcessQueue中移除。</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/ProcessQueue.png"
                      alt="img"
                ></p>
<p><strong>属性</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//消息容器</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> TreeMap&lt;Long, MessageExt&gt; msgTreeMap = <span class="keyword">new</span> <span class="title class_">TreeMap</span>&lt;Long, MessageExt&gt;();</span><br><span class="line"><span class="comment">//读写锁</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReadWriteLock</span> <span class="variable">lockTreeMap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantReadWriteLock</span>();</span><br><span class="line"><span class="comment">//ProcessQueue总消息树</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">AtomicLong</span> <span class="variable">msgCount</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicLong</span>();</span><br><span class="line"><span class="comment">//ProcessQueue队列最大偏移量</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> <span class="variable">queueOffsetMax</span> <span class="operator">=</span> <span class="number">0L</span>;</span><br><span class="line"><span class="comment">//当前ProcessQueue是否被丢弃</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">dropped</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"><span class="comment">//上一次拉取时间戳</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> <span class="variable">lastPullTimestamp</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"><span class="comment">//上一次消费时间戳</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">long</span> <span class="variable">lastConsumeTimestamp</span> <span class="operator">=</span> System.currentTimeMillis();</span><br></pre></td></tr></table></figure>



<p><strong>方法</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//移除消费超时消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cleanExpiredMsg</span><span class="params">(DefaultMQPushConsumer pushConsumer)</span></span><br><span class="line"><span class="comment">//添加消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">putMessage</span><span class="params">(<span class="keyword">final</span> List&lt;MessageExt&gt; msgs)</span></span><br><span class="line"><span class="comment">//获取消息最大间隔</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getMaxSpan</span><span class="params">()</span></span><br><span class="line"><span class="comment">//移除消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">removeMessage</span><span class="params">(<span class="keyword">final</span> List&lt;MessageExt&gt; msgs)</span></span><br><span class="line"><span class="comment">//将consumingMsgOrderlyTreeMap中消息重新放在msgTreeMap,并清空consumingMsgOrderlyTreeMap   </span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">rollback</span><span class="params">()</span> </span><br><span class="line"><span class="comment">//将consumingMsgOrderlyTreeMap消息清除,表示成功处理该批消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">long</span> <span class="title function_">commit</span><span class="params">()</span></span><br><span class="line"><span class="comment">//重新处理该批消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">makeMessageToCosumeAgain</span><span class="params">(List&lt;MessageExt&gt; msgs)</span> </span><br><span class="line"><span class="comment">//从processQueue中取出batchSize条消息</span></span><br><span class="line"><span class="keyword">public</span> List&lt;MessageExt&gt; <span class="title function_">takeMessags</span><span class="params">(<span class="keyword">final</span> <span class="type">int</span> batchSize)</span></span><br></pre></td></tr></table></figure>



<h4 id="3）消息拉取基本流程"><a href="#3）消息拉取基本流程" class="headerlink" title="3）消息拉取基本流程"></a>3）消息拉取基本流程</h4><p>#####1.客户端发起拉取请求</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B.png"
                      alt="img"
                ></p>
<p><em><strong>代码：DefaultMQPushConsumerImpl#pullMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">pullMessage</span><span class="params">(<span class="keyword">final</span> PullRequest pullRequest)</span> &#123;</span><br><span class="line">    <span class="comment">//从pullRequest获得ProcessQueue</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">ProcessQueue</span> <span class="variable">processQueue</span> <span class="operator">=</span> pullRequest.getProcessQueue();</span><br><span class="line">    <span class="comment">//如果处理队列被丢弃,直接返回</span></span><br><span class="line">    <span class="keyword">if</span> (processQueue.isDropped()) &#123;</span><br><span class="line">        log.info(<span class="string">&quot;the pull request[&#123;&#125;] is dropped.&quot;</span>, pullRequest.toString());</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//如果处理队列未被丢弃,更新时间戳</span></span><br><span class="line">    pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.makeSureStateOK();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (MQClientException e) &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;pullMessage exception, consumer state not ok&quot;</span>, e);</span><br><span class="line">        <span class="built_in">this</span>.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//如果处理队列被挂起,延迟1s后再执行</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.isPause()) &#123;</span><br><span class="line">        log.warn(<span class="string">&quot;consumer was paused, execute pull request later. instanceName=&#123;&#125;, group=&#123;&#125;&quot;</span>, <span class="built_in">this</span>.defaultMQPushConsumer.getInstanceName(), <span class="built_in">this</span>.defaultMQPushConsumer.getConsumerGroup());</span><br><span class="line">        <span class="built_in">this</span>.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_SUSPEND);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//获得最大待处理消息数量</span></span><br><span class="line">	<span class="type">long</span> <span class="variable">cachedMessageCount</span> <span class="operator">=</span> processQueue.getMsgCount().get();</span><br><span class="line">    <span class="comment">//获得最大待处理消息大小</span></span><br><span class="line">	<span class="type">long</span> <span class="variable">cachedMessageSizeInMiB</span> <span class="operator">=</span> processQueue.getMsgSize().get() / (<span class="number">1024</span> * <span class="number">1024</span>);</span><br><span class="line">	<span class="comment">//从数量进行流控</span></span><br><span class="line">	<span class="keyword">if</span> (cachedMessageCount &gt; <span class="built_in">this</span>.defaultMQPushConsumer.getPullThresholdForQueue()) &#123;</span><br><span class="line">	    <span class="built_in">this</span>.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);</span><br><span class="line">	    <span class="keyword">if</span> ((queueFlowControlTimes++ % <span class="number">1000</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">	        log.warn(</span><br><span class="line">	            <span class="string">&quot;the cached message count exceeds the threshold &#123;&#125;, so do flow control, minOffset=&#123;&#125;, maxOffset=&#123;&#125;, count=&#123;&#125;, size=&#123;&#125; MiB, pullRequest=&#123;&#125;, flowControlTimes=&#123;&#125;&quot;</span>,</span><br><span class="line">	            <span class="built_in">this</span>.defaultMQPushConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes);</span><br><span class="line">	    &#125;</span><br><span class="line">	    <span class="keyword">return</span>;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">//从消息大小进行流控</span></span><br><span class="line">	<span class="keyword">if</span> (cachedMessageSizeInMiB &gt; <span class="built_in">this</span>.defaultMQPushConsumer.getPullThresholdSizeForQueue()) &#123;</span><br><span class="line">	    <span class="built_in">this</span>.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL);</span><br><span class="line">	    <span class="keyword">if</span> ((queueFlowControlTimes++ % <span class="number">1000</span>) == <span class="number">0</span>) &#123;</span><br><span class="line">	        log.warn(</span><br><span class="line">	            <span class="string">&quot;the cached message size exceeds the threshold &#123;&#125; MiB, so do flow control, minOffset=&#123;&#125;, maxOffset=&#123;&#125;, count=&#123;&#125;, size=&#123;&#125; MiB, pullRequest=&#123;&#125;, flowControlTimes=&#123;&#125;&quot;</span>,</span><br><span class="line">	            <span class="built_in">this</span>.defaultMQPushConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes);</span><br><span class="line">	    &#125;</span><br><span class="line">	    <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    	<span class="comment">//获得订阅信息</span></span><br><span class="line">		 <span class="keyword">final</span> <span class="type">SubscriptionData</span> <span class="variable">subscriptionData</span> <span class="operator">=</span> <span class="built_in">this</span>.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic());</span><br><span class="line">    	<span class="keyword">if</span> (<span class="literal">null</span> == subscriptionData) &#123;</span><br><span class="line">    	    <span class="built_in">this</span>.executePullRequestLater(pullRequest, PULL_TIME_DELAY_MILLS_WHEN_EXCEPTION);</span><br><span class="line">    	    log.warn(<span class="string">&quot;find the consumer&#x27;s subscription failed, &#123;&#125;&quot;</span>, pullRequest);</span><br><span class="line">    	    <span class="keyword">return</span>;</span><br><span class="line">		<span class="comment">//与服务端交互,获取消息</span></span><br><span class="line">	    <span class="built_in">this</span>.pullAPIWrapper.pullKernelImpl(</span><br><span class="line">	    pullRequest.getMessageQueue(),</span><br><span class="line">	    subExpression,</span><br><span class="line">	    subscriptionData.getExpressionType(),</span><br><span class="line">	    subscriptionData.getSubVersion(),</span><br><span class="line">	    pullRequest.getNextOffset(),</span><br><span class="line">	    <span class="built_in">this</span>.defaultMQPushConsumer.getPullBatchSize(),</span><br><span class="line">	    sysFlag,</span><br><span class="line">	    commitOffsetValue,</span><br><span class="line">	    BROKER_SUSPEND_MAX_TIME_MILLIS,</span><br><span class="line">	    CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,</span><br><span class="line">	    CommunicationMode.ASYNC,</span><br><span class="line">	    pullCallback</span><br><span class="line">	);</span><br><span class="line">            </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>#####2.消息服务端Broker组装消息</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E6%9C%8D%E5%8A%A1%E7%AB%AFBroker%E7%BB%84%E8%A3%85%E6%B6%88%E6%81%AF.png"
                      alt="img"
                ></p>
<p><em><strong>代码：PullMessageProcessor#processRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//构建消息过滤器</span></span><br><span class="line">MessageFilter messageFilter;</span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.brokerController.getBrokerConfig().isFilterSupportRetry()) &#123;</span><br><span class="line">    messageFilter = <span class="keyword">new</span> <span class="title class_">ExpressionForRetryMessageFilter</span>(subscriptionData, consumerFilterData,</span><br><span class="line">        <span class="built_in">this</span>.brokerController.getConsumerFilterManager());</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    messageFilter = <span class="keyword">new</span> <span class="title class_">ExpressionMessageFilter</span>(subscriptionData, consumerFilterData,</span><br><span class="line">        <span class="built_in">this</span>.brokerController.getConsumerFilterManager());</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//调用MessageStore.getMessage查找消息</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">GetMessageResult</span> <span class="variable">getMessageResult</span> <span class="operator">=</span></span><br><span class="line">    <span class="built_in">this</span>.brokerController.getMessageStore().getMessage(</span><br><span class="line">    				requestHeader.getConsumerGroup(), <span class="comment">//消费组名称								</span></span><br><span class="line">    				requestHeader.getTopic(),	<span class="comment">//主题名称</span></span><br><span class="line">        			requestHeader.getQueueId(), <span class="comment">//队列ID</span></span><br><span class="line">    				requestHeader.getQueueOffset(), 	<span class="comment">//待拉取偏移量</span></span><br><span class="line">    				requestHeader.getMaxMsgNums(), 	<span class="comment">//最大拉取消息条数</span></span><br><span class="line">    				messageFilter	<span class="comment">//消息过滤器</span></span><br><span class="line">    		);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore#getMessage</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">GetMessageStatus</span> <span class="variable">status</span> <span class="operator">=</span> GetMessageStatus.NO_MESSAGE_IN_QUEUE;</span><br><span class="line"><span class="type">long</span> <span class="variable">nextBeginOffset</span> <span class="operator">=</span> offset;	<span class="comment">//查找下一次队列偏移量</span></span><br><span class="line"><span class="type">long</span> <span class="variable">minOffset</span> <span class="operator">=</span> <span class="number">0</span>;		<span class="comment">//当前消息队列最小偏移量</span></span><br><span class="line"><span class="type">long</span> <span class="variable">maxOffset</span> <span class="operator">=</span> <span class="number">0</span>;		<span class="comment">//当前消息队列最大偏移量</span></span><br><span class="line"><span class="type">GetMessageResult</span> <span class="variable">getResult</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GetMessageResult</span>();</span><br><span class="line"><span class="keyword">final</span> <span class="type">long</span> <span class="variable">maxOffsetPy</span> <span class="operator">=</span> <span class="built_in">this</span>.commitLog.getMaxOffset();	<span class="comment">//当前commitLog最大偏移量</span></span><br><span class="line"><span class="comment">//根据主题名称和队列编号获取消息消费队列</span></span><br><span class="line"><span class="type">ConsumeQueue</span> <span class="variable">consumeQueue</span> <span class="operator">=</span> findConsumeQueue(topic, queueId);</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line">minOffset = consumeQueue.getMinOffsetInQueue();</span><br><span class="line">maxOffset = consumeQueue.getMaxOffsetInQueue();</span><br><span class="line"><span class="comment">//消息偏移量异常情况校对下一次拉取偏移量</span></span><br><span class="line"><span class="keyword">if</span> (maxOffset == <span class="number">0</span>) &#123;	<span class="comment">//表示当前消息队列中没有消息</span></span><br><span class="line">    status = GetMessageStatus.NO_MESSAGE_IN_QUEUE;</span><br><span class="line">    nextBeginOffset = nextOffsetCorrection(offset, <span class="number">0</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (offset &lt; minOffset) &#123;	<span class="comment">//待拉取消息的偏移量小于队列的其实偏移量</span></span><br><span class="line">    status = GetMessageStatus.OFFSET_TOO_SMALL;</span><br><span class="line">    nextBeginOffset = nextOffsetCorrection(offset, minOffset);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (offset == maxOffset) &#123;	<span class="comment">//待拉取偏移量为队列最大偏移量</span></span><br><span class="line">    status = GetMessageStatus.OFFSET_OVERFLOW_ONE;</span><br><span class="line">    nextBeginOffset = nextOffsetCorrection(offset, offset);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (offset &gt; maxOffset) &#123;	<span class="comment">//偏移量越界</span></span><br><span class="line">    status = GetMessageStatus.OFFSET_OVERFLOW_BADLY;</span><br><span class="line">    <span class="keyword">if</span> (<span class="number">0</span> == minOffset) &#123;</span><br><span class="line">        nextBeginOffset = nextOffsetCorrection(offset, minOffset);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        nextBeginOffset = nextOffsetCorrection(offset, maxOffset);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="comment">//根据偏移量从CommitLog中拉取32条消息</span></span><br><span class="line"><span class="type">SelectMappedBufferResult</span> <span class="variable">selectResult</span> <span class="operator">=</span> <span class="built_in">this</span>.commitLog.getMessage(offsetPy, sizePy);</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：PullMessageProcessor#processRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//根据拉取结果填充responseHeader</span></span><br><span class="line">response.setRemark(getMessageResult.getStatus().name());</span><br><span class="line">responseHeader.setNextBeginOffset(getMessageResult.getNextBeginOffset());</span><br><span class="line">responseHeader.setMinOffset(getMessageResult.getMinOffset());</span><br><span class="line">responseHeader.setMaxOffset(getMessageResult.getMaxOffset());</span><br><span class="line"></span><br><span class="line"><span class="comment">//判断如果存在主从同步慢,设置下一次拉取任务的ID为主节点</span></span><br><span class="line"><span class="keyword">switch</span> (<span class="built_in">this</span>.brokerController.getMessageStoreConfig().getBrokerRole()) &#123;</span><br><span class="line">    <span class="keyword">case</span> ASYNC_MASTER:</span><br><span class="line">    <span class="keyword">case</span> SYNC_MASTER:</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> SLAVE:</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">this</span>.brokerController.getBrokerConfig().isSlaveReadEnable()) &#123;</span><br><span class="line">            response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);</span><br><span class="line">            responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="comment">//GetMessageResult与Response的Code转换</span></span><br><span class="line"><span class="keyword">switch</span> (getMessageResult.getStatus()) &#123;</span><br><span class="line">    <span class="keyword">case</span> FOUND:			<span class="comment">//成功</span></span><br><span class="line">        response.setCode(ResponseCode.SUCCESS);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> MESSAGE_WAS_REMOVING:	<span class="comment">//消息存放在下一个commitLog中</span></span><br><span class="line">        response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);	<span class="comment">//消息重试</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> NO_MATCHED_LOGIC_QUEUE:	<span class="comment">//未找到队列</span></span><br><span class="line">    <span class="keyword">case</span> NO_MESSAGE_IN_QUEUE:	<span class="comment">//队列中未包含消息</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="number">0</span> != requestHeader.getQueueOffset()) &#123;</span><br><span class="line">            response.setCode(ResponseCode.PULL_OFFSET_MOVED);</span><br><span class="line">            requestHeader.getQueueOffset(),</span><br><span class="line">            getMessageResult.getNextBeginOffset(),</span><br><span class="line">            requestHeader.getTopic(),</span><br><span class="line">            requestHeader.getQueueId(),</span><br><span class="line">            requestHeader.getConsumerGroup()</span><br><span class="line">            );</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            response.setCode(ResponseCode.PULL_NOT_FOUND);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> NO_MATCHED_MESSAGE:	<span class="comment">//未找到消息</span></span><br><span class="line">        response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> OFFSET_FOUND_NULL:	<span class="comment">//消息物理偏移量为空</span></span><br><span class="line">        response.setCode(ResponseCode.PULL_NOT_FOUND);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> OFFSET_OVERFLOW_BADLY:	<span class="comment">//offset越界</span></span><br><span class="line">        response.setCode(ResponseCode.PULL_OFFSET_MOVED);</span><br><span class="line">        <span class="comment">// <span class="doctag">XXX:</span> warn and notify me</span></span><br><span class="line">        log.info(<span class="string">&quot;the request offset: &#123;&#125; over flow badly, broker max offset: &#123;&#125;, consumer: &#123;&#125;&quot;</span>,</span><br><span class="line">                requestHeader.getQueueOffset(), getMessageResult.getMaxOffset(), channel.remoteAddress());</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> OFFSET_OVERFLOW_ONE:	<span class="comment">//offset在队列中未找到</span></span><br><span class="line">        response.setCode(ResponseCode.PULL_NOT_FOUND);</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> OFFSET_TOO_SMALL:	<span class="comment">//offset未在队列中</span></span><br><span class="line">        response.setCode(ResponseCode.PULL_OFFSET_MOVED);</span><br><span class="line">        requestHeader.getConsumerGroup(), </span><br><span class="line">        requestHeader.getTopic(), </span><br><span class="line">        requestHeader.getQueueOffset(),</span><br><span class="line">        getMessageResult.getMinOffset(), channel.remoteAddress());</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">default</span>:</span><br><span class="line">        <span class="keyword">assert</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="comment">//如果CommitLog标记可用,并且当前Broker为主节点,则更新消息消费进度</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">storeOffsetEnable</span> <span class="operator">=</span> brokerAllowSuspend;</span><br><span class="line">storeOffsetEnable = storeOffsetEnable &amp;&amp; hasCommitOffsetFlag;</span><br><span class="line">storeOffsetEnable = storeOffsetEnable</span><br><span class="line">    &amp;&amp; <span class="built_in">this</span>.brokerController.getMessageStoreConfig().getBrokerRole() != BrokerRole.SLAVE;</span><br><span class="line"><span class="keyword">if</span> (storeOffsetEnable) &#123;</span><br><span class="line">    <span class="built_in">this</span>.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel),</span><br><span class="line">        requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>#####3.消息拉取客户端处理消息</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%A4%84%E7%90%86%E6%B6%88%E6%81%AF.png"
                      alt="img"
                ></p>
<p><em><strong>代码：MQClientAPIImpl#processPullResponse</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> PullResult <span class="title function_">processPullResponse</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> RemotingCommand response)</span> <span class="keyword">throws</span> MQBrokerException, RemotingCommandException &#123;</span><br><span class="line">    <span class="type">PullStatus</span> <span class="variable">pullStatus</span> <span class="operator">=</span> PullStatus.NO_NEW_MSG;</span><br><span class="line">   	<span class="comment">//判断响应结果</span></span><br><span class="line">    <span class="keyword">switch</span> (response.getCode()) &#123;</span><br><span class="line">        <span class="keyword">case</span> ResponseCode.SUCCESS:</span><br><span class="line">            pullStatus = PullStatus.FOUND;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> ResponseCode.PULL_NOT_FOUND:</span><br><span class="line">            pullStatus = PullStatus.NO_NEW_MSG;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> ResponseCode.PULL_RETRY_IMMEDIATELY:</span><br><span class="line">            pullStatus = PullStatus.NO_MATCHED_MSG;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> ResponseCode.PULL_OFFSET_MOVED:</span><br><span class="line">            pullStatus = PullStatus.OFFSET_ILLEGAL;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">MQBrokerException</span>(response.getCode(), response.getRemark());</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="comment">//解码响应头</span></span><br><span class="line">    <span class="type">PullMessageResponseHeader</span> <span class="variable">responseHeader</span> <span class="operator">=</span></span><br><span class="line">        (PullMessageResponseHeader) response.decodeCommandCustomHeader(PullMessageResponseHeader.class);</span><br><span class="line">	<span class="comment">//封装PullResultExt返回</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">PullResultExt</span>(pullStatus, responseHeader.getNextBeginOffset(), responseHeader.getMinOffset(),</span><br><span class="line">        responseHeader.getMaxOffset(), <span class="literal">null</span>, responseHeader.getSuggestWhichBrokerId(), response.getBody());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><strong>PullResult类</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> PullStatus pullStatus;	<span class="comment">//拉取结果</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> nextBeginOffset;	<span class="comment">//下次拉取偏移量</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> minOffset;	<span class="comment">//消息队列最小偏移量</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">long</span> maxOffset;	<span class="comment">//消息队列最大偏移量</span></span><br><span class="line"><span class="keyword">private</span> List&lt;MessageExt&gt; msgFoundList;	<span class="comment">//拉取的消息列表</span></span><br></pre></td></tr></table></figure>



<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/PullStatus.png"
                      alt="img"
                ></p>
<p><em><strong>代码：DefaultMQPushConsumerImpl$PullCallback#OnSuccess</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//将拉取到的消息存入processQueue</span></span><br><span class="line"><span class="type">boolean</span> <span class="variable">dispatchToConsume</span> <span class="operator">=</span> processQueue.putMessage(pullResult.getMsgFoundList());</span><br><span class="line"><span class="comment">//将processQueue提交到consumeMessageService中供消费者消费</span></span><br><span class="line">DefaultMQPushConsumerImpl.<span class="built_in">this</span>.consumeMessageService.submitConsumeRequest(</span><br><span class="line">    pullResult.getMsgFoundList(),</span><br><span class="line">    processQueue,</span><br><span class="line">    pullRequest.getMessageQueue(),</span><br><span class="line">    dispatchToConsume);</span><br><span class="line"><span class="comment">//如果pullInterval大于0,则等待pullInterval毫秒后将pullRequest对象放入到PullMessageService中的pullRequestQueue队列中</span></span><br><span class="line"><span class="keyword">if</span> (DefaultMQPushConsumerImpl.<span class="built_in">this</span>.defaultMQPushConsumer.getPullInterval() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    DefaultMQPushConsumerImpl.<span class="built_in">this</span>.executePullRequestLater(pullRequest,</span><br><span class="line">        DefaultMQPushConsumerImpl.<span class="built_in">this</span>.defaultMQPushConsumer.getPullInterval());</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    DefaultMQPushConsumerImpl.<span class="built_in">this</span>.executePullRequestImmediately(pullRequest);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h5 id="4-消息拉取总结"><a href="#4-消息拉取总结" class="headerlink" title="4.消息拉取总结"></a>4.消息拉取总结</h5><p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96%E6%B5%81%E7%A8%8B%E6%80%BB%E7%BB%93.png"
                      alt="img"
                ></p>
<h4 id="4）消息拉取长轮询机制分析"><a href="#4）消息拉取长轮询机制分析" class="headerlink" title="4）消息拉取长轮询机制分析"></a>4）消息拉取长轮询机制分析</h4><p>RocketMQ未真正实现消息推模式，而是消费者主动向消息服务器拉取消息，RocketMQ推模式是循环向消息服务端发起消息拉取请求，如果消息消费者向RocketMQ拉取消息时，消息未到达消费队列时，如果不启用长轮询机制，则会在服务端等待shortPollingTimeMills时间后（挂起）再去判断消息是否已经到达指定消息队列，如果消息仍未到达则提示拉取消息客户端PULL—NOT—FOUND（消息不存在）；如果开启长轮询模式，RocketMQ一方面会每隔5s轮询检查一次消息是否可达，同时一有消息达到后立马通知挂起线程再次验证消息是否是自己感兴趣的消息，如果是则从CommitLog文件中提取消息返回给消息拉取客户端，否则直到挂起超时，超时时间由消息拉取方在消息拉取是封装在请求参数中，PUSH模式为15s，PULL模式通过DefaultMQPullConsumer#setBrokerSuspendMaxTimeMillis设置。RocketMQ通过在Broker客户端配置longPollingEnable为true来开启长轮询模式。</p>
<p><em><strong>代码：PullMessageProcessor#processRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//当没有拉取到消息时，通过长轮询方式继续拉取消息</span></span><br><span class="line"><span class="keyword">case</span> ResponseCode.PULL_NOT_FOUND:</span><br><span class="line">    <span class="keyword">if</span> (brokerAllowSuspend &amp;&amp; hasSuspendFlag) &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">pollingTimeMills</span> <span class="operator">=</span> suspendTimeoutMillisLong;</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">this</span>.brokerController.getBrokerConfig().isLongPollingEnable()) &#123;</span><br><span class="line">            pollingTimeMills = <span class="built_in">this</span>.brokerController.getBrokerConfig().getShortPollingTimeMills();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> requestHeader.getTopic();</span><br><span class="line">        <span class="type">long</span> <span class="variable">offset</span> <span class="operator">=</span> requestHeader.getQueueOffset();</span><br><span class="line">        <span class="type">int</span> <span class="variable">queueId</span> <span class="operator">=</span> requestHeader.getQueueId();</span><br><span class="line">        <span class="comment">//构建拉取请求对象</span></span><br><span class="line">        <span class="type">PullRequest</span> <span class="variable">pullRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PullRequest</span>(request, channel, pollingTimeMills,</span><br><span class="line">            <span class="built_in">this</span>.brokerController.getMessageStore().now(), offset, subscriptionData, messageFilter);</span><br><span class="line">        <span class="comment">//处理拉取请求</span></span><br><span class="line">        <span class="built_in">this</span>.brokerController.getPullRequestHoldService().suspendPullRequest(topic, queueId, pullRequest);</span><br><span class="line">        response = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>



<p><strong>PullRequestHoldService方式实现长轮询</strong></p>
<p><em><strong>代码：PullRequestHoldService#suspendPullRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//将拉取消息请求，放置在ManyPullRequest集合中</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">suspendPullRequest</span><span class="params">(<span class="keyword">final</span> String topic, <span class="keyword">final</span> <span class="type">int</span> queueId, <span class="keyword">final</span> PullRequest pullRequest)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> <span class="built_in">this</span>.buildKey(topic, queueId);</span><br><span class="line">    <span class="type">ManyPullRequest</span> <span class="variable">mpr</span> <span class="operator">=</span> <span class="built_in">this</span>.pullRequestTable.get(key);</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == mpr) &#123;</span><br><span class="line">        mpr = <span class="keyword">new</span> <span class="title class_">ManyPullRequest</span>();</span><br><span class="line">        <span class="type">ManyPullRequest</span> <span class="variable">prev</span> <span class="operator">=</span> <span class="built_in">this</span>.pullRequestTable.putIfAbsent(key, mpr);</span><br><span class="line">        <span class="keyword">if</span> (prev != <span class="literal">null</span>) &#123;</span><br><span class="line">            mpr = prev;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mpr.addPullRequest(pullRequest);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：PullRequestHoldService#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    log.info(<span class="string">&quot;&#123;&#125; service started&quot;</span>, <span class="built_in">this</span>.getServiceName());</span><br><span class="line">    <span class="keyword">while</span> (!<span class="built_in">this</span>.isStopped()) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//如果开启长轮询每隔5秒判断消息是否到达</span></span><br><span class="line">            <span class="keyword">if</span> (<span class="built_in">this</span>.brokerController.getBrokerConfig().isLongPollingEnable()) &#123;</span><br><span class="line">                <span class="built_in">this</span>.waitForRunning(<span class="number">5</span> * <span class="number">1000</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">//没有开启长轮询,每隔1s再次尝试</span></span><br><span class="line">              <span class="built_in">this</span>.waitForRunning(<span class="built_in">this</span>.brokerController.getBrokerConfig().getShortPollingTimeMills());</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="type">long</span> <span class="variable">beginLockTimestamp</span> <span class="operator">=</span> <span class="built_in">this</span>.systemClock.now();</span><br><span class="line">            <span class="built_in">this</span>.checkHoldRequest();</span><br><span class="line">            <span class="type">long</span> <span class="variable">costTime</span> <span class="operator">=</span> <span class="built_in">this</span>.systemClock.now() - beginLockTimestamp;</span><br><span class="line">            <span class="keyword">if</span> (costTime &gt; <span class="number">5</span> * <span class="number">1000</span>) &#123;</span><br><span class="line">                log.info(<span class="string">&quot;[NOTIFYME] check hold request cost &#123;&#125; ms.&quot;</span>, costTime);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            log.warn(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service has exception. &quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.info(<span class="string">&quot;&#123;&#125; service end&quot;</span>, <span class="built_in">this</span>.getServiceName());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：PullRequestHoldService#checkHoldRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//遍历拉取任务</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">checkHoldRequest</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (String key : <span class="built_in">this</span>.pullRequestTable.keySet()) &#123;</span><br><span class="line">        String[] kArray = key.split(TOPIC_QUEUEID_SEPARATOR);</span><br><span class="line">        <span class="keyword">if</span> (<span class="number">2</span> == kArray.length) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> kArray[<span class="number">0</span>];</span><br><span class="line">            <span class="type">int</span> <span class="variable">queueId</span> <span class="operator">=</span> Integer.parseInt(kArray[<span class="number">1</span>]);</span><br><span class="line">            <span class="comment">//获得消息偏移量</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">long</span> <span class="variable">offset</span> <span class="operator">=</span> <span class="built_in">this</span>.brokerController.getMessageStore().getMaxOffsetInQueue(topic, queueId);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//通知有消息达到</span></span><br><span class="line">                <span class="built_in">this</span>.notifyMessageArriving(topic, queueId, offset);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;check hold request failed. topic=&#123;&#125;, queueId=&#123;&#125;&quot;</span>, topic, queueId, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：PullRequestHoldService#notifyMessageArriving</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//如果拉取消息偏移大于请求偏移量,如果消息匹配调用executeRequestWhenWakeup处理消息</span></span><br><span class="line"><span class="keyword">if</span> (newestOffset &gt; request.getPullFromThisOffset()) &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">match</span> <span class="operator">=</span> request.getMessageFilter().isMatchedByConsumeQueue(tagsCode,</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">ConsumeQueueExt</span>.CqExtUnit(tagsCode, msgStoreTime, filterBitMap));</span><br><span class="line">    <span class="comment">// match by bit map, need eval again when properties is not null.</span></span><br><span class="line">    <span class="keyword">if</span> (match &amp;&amp; properties != <span class="literal">null</span>) &#123;</span><br><span class="line">        match = request.getMessageFilter().isMatchedByCommitLog(<span class="literal">null</span>, properties);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (match) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(),</span><br><span class="line">                request.getRequestCommand());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;execute request when wakeup failed.&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//如果过期时间超时,则不继续等待将直接返回给客户端消息未找到</span></span><br><span class="line"><span class="keyword">if</span> (System.currentTimeMillis() &gt;= (request.getSuspendTimestamp() + request.getTimeoutMillis())) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.brokerController.getPullMessageProcessor().executeRequestWhenWakeup(request.getClientChannel(),</span><br><span class="line">            request.getRequestCommand());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;execute request when wakeup failed.&quot;</span>, e);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>如果开启了长轮询机制，PullRequestHoldService会每隔5s被唤醒去尝试检测是否有新的消息的到来才给客户端响应，或者直到超时才给客户端进行响应，消息实时性比较差，为了避免这种情况，RocketMQ引入另外一种机制：当消息到达时唤醒挂起线程触发一次检查。</p>
<p><strong>DefaultMessageStore$ReputMessageService机制</strong></p>
<p><em><strong>代码：DefaultMessageStore#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//长轮询入口</span></span><br><span class="line"><span class="built_in">this</span>.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);</span><br><span class="line"><span class="built_in">this</span>.reputMessageService.start();</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore$ReputMessageService#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    DefaultMessageStore.log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service started&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (!<span class="built_in">this</span>.isStopped()) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1</span>);</span><br><span class="line">            <span class="comment">//长轮询核心逻辑代码入口</span></span><br><span class="line">            <span class="built_in">this</span>.doReput();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            DefaultMessageStore.log.warn(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service has exception. &quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    DefaultMessageStore.log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service end&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：DefaultMessageStore$ReputMessageService#deReput</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//当新消息达到是,进行通知监听器进行处理</span></span><br><span class="line"><span class="keyword">if</span> (BrokerRole.SLAVE != DefaultMessageStore.<span class="built_in">this</span>.getMessageStoreConfig().getBrokerRole()</span><br><span class="line">    &amp;&amp; DefaultMessageStore.<span class="built_in">this</span>.brokerConfig.isLongPollingEnable()) &#123;</span><br><span class="line">    DefaultMessageStore.<span class="built_in">this</span>.messageArrivingListener.arriving(dispatchRequest.getTopic(),</span><br><span class="line">        dispatchRequest.getQueueId(), dispatchRequest.getConsumeQueueOffset() + <span class="number">1</span>,</span><br><span class="line">        dispatchRequest.getTagsCode(), dispatchRequest.getStoreTimestamp(),</span><br><span class="line">        dispatchRequest.getBitMap(), dispatchRequest.getPropertiesMap());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：NotifyMessageArrivingListener#arriving</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">arriving</span><span class="params">(String topic, <span class="type">int</span> queueId, <span class="type">long</span> logicOffset, <span class="type">long</span> tagsCode,</span></span><br><span class="line"><span class="params">    <span class="type">long</span> msgStoreTime, <span class="type">byte</span>[] filterBitMap, Map&lt;String, String&gt; properties)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.pullRequestHoldService.notifyMessageArriving(topic, queueId, logicOffset, tagsCode,</span><br><span class="line">        msgStoreTime, filterBitMap, properties);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-5-5-消息队列负载与重新分布机制"><a href="#2-5-5-消息队列负载与重新分布机制" class="headerlink" title="2.5.5 消息队列负载与重新分布机制"></a>2.5.5 消息队列负载与重新分布机制</h3><p>RocketMQ消息队列重新分配是由RebalanceService线程来实现。一个MQClientInstance持有一个RebalanceService实现，并随着MQClientInstance的启动而启动。</p>
<p><em><strong>代码：RebalanceService#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service started&quot;</span>);</span><br><span class="line">	<span class="comment">//RebalanceService线程默认每隔20s执行一次mqClientFactory.doRebalance方法</span></span><br><span class="line">    <span class="keyword">while</span> (!<span class="built_in">this</span>.isStopped()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.waitForRunning(waitInterval);</span><br><span class="line">        <span class="built_in">this</span>.mqClientFactory.doRebalance();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    log.info(<span class="built_in">this</span>.getServiceName() + <span class="string">&quot; service end&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：MQClientInstance#doRebalance</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doRebalance</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//MQClientInstance遍历以注册的消费者,对消费者执行doRebalance()方法</span></span><br><span class="line">    <span class="keyword">for</span> (Map.Entry&lt;String, MQConsumerInner&gt; entry : <span class="built_in">this</span>.consumerTable.entrySet()) &#123;</span><br><span class="line">        <span class="type">MQConsumerInner</span> <span class="variable">impl</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">        <span class="keyword">if</span> (impl != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                impl.doRebalance();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">                log.error(<span class="string">&quot;doRebalance exception&quot;</span>, e);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：RebalanceImpl#doRebalance</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//遍历订阅消息对每个主题的订阅的队列进行重新负载</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doRebalance</span><span class="params">(<span class="keyword">final</span> <span class="type">boolean</span> isOrder)</span> &#123;</span><br><span class="line">    Map&lt;String, SubscriptionData&gt; subTable = <span class="built_in">this</span>.getSubscriptionInner();</span><br><span class="line">    <span class="keyword">if</span> (subTable != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">final</span> Map.Entry&lt;String, SubscriptionData&gt; entry : subTable.entrySet()) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">String</span> <span class="variable">topic</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="built_in">this</span>.rebalanceByTopic(topic, isOrder);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">                <span class="keyword">if</span> (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) &#123;</span><br><span class="line">                    log.warn(<span class="string">&quot;rebalanceByTopic Exception&quot;</span>, e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.truncateMessageQueueNotMyTopic();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：RebalanceImpl#rebalanceByTopic</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//从主题订阅消息缓存表中获取主题的队列信息</span></span><br><span class="line">Set&lt;MessageQueue&gt; mqSet = <span class="built_in">this</span>.topicSubscribeInfoTable.get(topic);</span><br><span class="line"><span class="comment">//查找该主题订阅组所有的消费者ID</span></span><br><span class="line">List&lt;String&gt; cidAll = <span class="built_in">this</span>.mQClientFactory.findConsumerIdList(topic, consumerGroup);</span><br><span class="line"></span><br><span class="line"><span class="comment">//给消费者重新分配队列</span></span><br><span class="line"><span class="keyword">if</span> (mqSet != <span class="literal">null</span> &amp;&amp; cidAll != <span class="literal">null</span>) &#123;</span><br><span class="line">    List&lt;MessageQueue&gt; mqAll = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;MessageQueue&gt;();</span><br><span class="line">    mqAll.addAll(mqSet);</span><br><span class="line"></span><br><span class="line">    Collections.sort(mqAll);</span><br><span class="line">    Collections.sort(cidAll);</span><br><span class="line"></span><br><span class="line">    <span class="type">AllocateMessageQueueStrategy</span> <span class="variable">strategy</span> <span class="operator">=</span> <span class="built_in">this</span>.allocateMessageQueueStrategy;</span><br><span class="line"></span><br><span class="line">    List&lt;MessageQueue&gt; allocateResult = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        allocateResult = strategy.allocate(</span><br><span class="line">            <span class="built_in">this</span>.consumerGroup,</span><br><span class="line">            <span class="built_in">this</span>.mQClientFactory.getClientId(),</span><br><span class="line">            mqAll,</span><br><span class="line">            cidAll);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">        log.error(<span class="string">&quot;AllocateMessageQueueStrategy.allocate Exception. allocateMessageQueueStrategyName=&#123;&#125;&quot;</span>, strategy.getName(),</span><br><span class="line">            e);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>




<p>RocketMQ默认提供5中负载均衡分配算法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">AllocateMessageQueueAveragely:平均分配</span><br><span class="line">举例:<span class="number">8</span>个队列q1,q2,q3,q4,q5,a6,q7,q8,消费者<span class="number">3</span>个:c1,c2,c3</span><br><span class="line">分配如下:</span><br><span class="line">c1:q1,q2,q3</span><br><span class="line">c2:q4,q5,a6</span><br><span class="line">c3:q7,q8</span><br><span class="line">AllocateMessageQueueAveragelyByCircle:平均轮询分配</span><br><span class="line">举例:<span class="number">8</span>个队列q1,q2,q3,q4,q5,a6,q7,q8,消费者<span class="number">3</span>个:c1,c2,c3</span><br><span class="line">分配如下:</span><br><span class="line">c1:q1,q4,q7</span><br><span class="line">c2:q2,q5,a8</span><br><span class="line">c3:q3,q6</span><br></pre></td></tr></table></figure>



<p>注意：消息队列的分配遵循一个消费者可以分配到多个队列，但同一个消息队列只会分配给一个消费者，故如果出现消费者个数大于消息队列数量，则有些消费者无法消费消息。</p>
<h3 id="2-5-6-消息消费过程"><a href="#2-5-6-消息消费过程" class="headerlink" title="2.5.6 消息消费过程"></a>2.5.6 消息消费过程</h3><p>PullMessageService负责对消息队列进行消息拉取，从远端服务器拉取消息后将消息存储ProcessQueue消息队列处理队列中，然后调用ConsumeMessageService#submitConsumeRequest方法进行消息消费，使用线程池来消费消息，确保了消息拉取与消息消费的解耦。ConsumeMessageService支持顺序消息和并发消息，核心类图如下：</p>
<p><img  
                     lazyload
                     alt="image"
                     data-src="https://pchaoo.gitee.io/blog/img/rocketmq/ConsumeMessageService.png"
                      alt="img"
                ></p>
<p><strong>并发消息消费</strong></p>
<p><em><strong>代码：ConsumeMessageConcurrentlyService#submitConsumeRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//消息批次单次</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">consumeBatchSize</span> <span class="operator">=</span> <span class="built_in">this</span>.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();</span><br><span class="line"><span class="comment">//msgs.size()默认最多为32条。</span></span><br><span class="line"><span class="comment">//如果msgs.size()小于consumeBatchSize,则直接将拉取到的消息放入到consumeRequest,然后将consumeRequest提交到消费者线程池中</span></span><br><span class="line"><span class="keyword">if</span> (msgs.size() &lt;= consumeBatchSize) &#123;</span><br><span class="line">    <span class="type">ConsumeRequest</span> <span class="variable">consumeRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConsumeRequest</span>(msgs, processQueue, messageQueue);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.consumeExecutor.submit(consumeRequest);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RejectedExecutionException e) &#123;</span><br><span class="line">        <span class="built_in">this</span>.submitConsumeRequestLater(consumeRequest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;	<span class="comment">//如果拉取的消息条数大于consumeBatchSize,则对拉取消息进行分页</span></span><br><span class="line">       <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> <span class="number">0</span>; total &lt; msgs.size(); ) &#123;</span><br><span class="line">   		    List&lt;MessageExt&gt; msgThis = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;MessageExt&gt;(consumeBatchSize);</span><br><span class="line">   		    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; consumeBatchSize; i++, total++) &#123;</span><br><span class="line">   		        <span class="keyword">if</span> (total &lt; msgs.size()) &#123;</span><br><span class="line">   		            msgThis.add(msgs.get(total));</span><br><span class="line">   		        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">   		            <span class="keyword">break</span>;</span><br><span class="line">   		        &#125;</span><br><span class="line">   		</span><br><span class="line">   		    <span class="type">ConsumeRequest</span> <span class="variable">consumeRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConsumeRequest</span>(msgThis, processQueue, messageQueue);</span><br><span class="line">   		    <span class="keyword">try</span> &#123;</span><br><span class="line">   		        <span class="built_in">this</span>.consumeExecutor.submit(consumeRequest);</span><br><span class="line">   		    &#125; <span class="keyword">catch</span> (RejectedExecutionException e) &#123;</span><br><span class="line">   		        <span class="keyword">for</span> (; total &lt; msgs.size(); total++) &#123;</span><br><span class="line">   		            msgThis.add(msgs.get(total));</span><br><span class="line">   		 </span><br><span class="line">   		        <span class="built_in">this</span>.submitConsumeRequestLater(consumeRequest);</span><br><span class="line">   		    &#125;</span><br><span class="line">   		&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：ConsumeMessageConcurrentlyService$ConsumeRequest#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//检查processQueue的dropped,如果为true,则停止该队列消费。</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.processQueue.isDropped()) &#123;</span><br><span class="line">    log.info(<span class="string">&quot;the message queue not be able to consume, because it&#x27;s dropped. group=&#123;&#125; &#123;&#125;&quot;</span>, ConsumeMessageConcurrentlyService.<span class="built_in">this</span>.consumerGroup, <span class="built_in">this</span>.messageQueue);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"><span class="comment">//执行消息处理的钩子函数</span></span><br><span class="line"><span class="keyword">if</span> (ConsumeMessageConcurrentlyService.<span class="built_in">this</span>.defaultMQPushConsumerImpl.hasHook()) &#123;</span><br><span class="line">    consumeMessageContext = <span class="keyword">new</span> <span class="title class_">ConsumeMessageContext</span>();</span><br><span class="line">    consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace());</span><br><span class="line">    consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup());</span><br><span class="line">    consumeMessageContext.setProps(<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;());</span><br><span class="line">    consumeMessageContext.setMq(messageQueue);</span><br><span class="line">    consumeMessageContext.setMsgList(msgs);</span><br><span class="line">    consumeMessageContext.setSuccess(<span class="literal">false</span>);</span><br><span class="line">    ConsumeMessageConcurrentlyService.<span class="built_in">this</span>.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext);</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line"><span class="comment">//调用应用程序消息监听器的consumeMessage方法,进入到具体的消息消费业务处理逻辑</span></span><br><span class="line">status = listener.consumeMessage(Collections.unmodifiableList(msgs), context);</span><br><span class="line"></span><br><span class="line"><span class="comment">//执行消息处理后的钩子函数</span></span><br><span class="line"><span class="keyword">if</span> (ConsumeMessageConcurrentlyService.<span class="built_in">this</span>.defaultMQPushConsumerImpl.hasHook()) &#123;</span><br><span class="line">    consumeMessageContext.setStatus(status.toString());</span><br><span class="line">    consumeMessageContext.setSuccess(ConsumeConcurrentlyStatus.CONSUME_SUCCESS == status);</span><br><span class="line">    ConsumeMessageConcurrentlyService.<span class="built_in">this</span>.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-5-7-定时消息机制"><a href="#2-5-7-定时消息机制" class="headerlink" title="2.5.7 定时消息机制"></a>2.5.7 定时消息机制</h3><p>定时消息是消息发送到Broker后，并不立即被消费者消费而是要等到特定的时间后才能被消费，RocketMQ并不支持任意的时间精度，如果要支持任意时间精度定时调度，不可避免地需要在Broker层做消息排序，再加上持久化方面的考量，将不可避免的带来巨大的性能消耗，所以RocketMQ只支持特定级别的延迟消息。消息延迟级别在Broker端通过messageDelayLevel配置，默认为“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”，delayLevel&#x3D;1表示延迟消息1s,delayLevel&#x3D;2表示延迟5s,依次类推。</p>
<p>RocketMQ定时消息实现类为ScheduleMessageService，该类在DefaultMessageStore中创建。通过在DefaultMessageStore中调用load方法加载该类并调用start方法启动。</p>
<p><em><strong>代码：ScheduleMessageService#load</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//加载延迟消息消费进度的加载与delayLevelTable的构造。延迟消息的进度默认存储路径为/store/config/delayOffset.json</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">load</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">result</span> <span class="operator">=</span> <span class="built_in">super</span>.load();</span><br><span class="line">    result = result &amp;&amp; <span class="built_in">this</span>.parseDelayLevel();</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：ScheduleMessageService#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//遍历延迟队列创建定时任务,遍历延迟级别，根据延迟级别level从offsetTable中获取消费队列的消费进度。如果不存在，则使用0</span></span><br><span class="line"><span class="keyword">for</span> (Map.Entry&lt;Integer, Long&gt; entry : <span class="built_in">this</span>.delayLevelTable.entrySet()) &#123;</span><br><span class="line">    <span class="type">Integer</span> <span class="variable">level</span> <span class="operator">=</span> entry.getKey();</span><br><span class="line">    <span class="type">Long</span> <span class="variable">timeDelay</span> <span class="operator">=</span> entry.getValue();</span><br><span class="line">    <span class="type">Long</span> <span class="variable">offset</span> <span class="operator">=</span> <span class="built_in">this</span>.offsetTable.get(level);</span><br><span class="line">    <span class="keyword">if</span> (<span class="literal">null</span> == offset) &#123;</span><br><span class="line">        offset = <span class="number">0L</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (timeDelay != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>.timer.schedule(<span class="keyword">new</span> <span class="title class_">DeliverDelayedMessageTimerTask</span>(level, offset), FIRST_DELAY_TIME);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//每隔10s持久化一次延迟队列的消息消费进度</span></span><br><span class="line"><span class="built_in">this</span>.timer.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">TimerTask</span>() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (started.get()) ScheduleMessageService.<span class="built_in">this</span>.persist();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;scheduleAtFixedRate flush exception&quot;</span>, e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="number">10000</span>, <span class="built_in">this</span>.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval());</span><br></pre></td></tr></table></figure>



<p><strong>调度机制</strong></p>
<p>ScheduleMessageService的start方法启动后，会为每一个延迟级别创建一个调度任务，每一个延迟级别对应SCHEDULE_TOPIC_XXXX主题下的一个消息消费队列。定时调度任务的实现类为DeliverDelayedMessageTimerTask，核心实现方法为executeOnTimeup</p>
<p><em><strong>代码：ScheduleMessageService$DeliverDelayedMessageTimerTask#executeOnTimeup</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//根据队列ID与延迟主题查找消息消费队列</span></span><br><span class="line"><span class="type">ConsumeQueue</span> <span class="variable">cq</span> <span class="operator">=</span></span><br><span class="line">    ScheduleMessageService.<span class="built_in">this</span>.defaultMessageStore.findConsumeQueue(SCHEDULE_TOPIC,</span><br><span class="line">        delayLevel2QueueId(delayLevel));</span><br><span class="line">...</span><br><span class="line"><span class="comment">//根据偏移量从消息消费队列中获取当前队列中所有有效的消息</span></span><br><span class="line"><span class="type">SelectMappedBufferResult</span> <span class="variable">bufferCQ</span> <span class="operator">=</span> cq.getIndexBuffer(<span class="built_in">this</span>.offset);</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"><span class="comment">//遍历ConsumeQueue,解析消息队列中消息</span></span><br><span class="line"><span class="keyword">for</span> (; i &lt; bufferCQ.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) &#123;</span><br><span class="line">    <span class="type">long</span> <span class="variable">offsetPy</span> <span class="operator">=</span> bufferCQ.getByteBuffer().getLong();</span><br><span class="line">    <span class="type">int</span> <span class="variable">sizePy</span> <span class="operator">=</span> bufferCQ.getByteBuffer().getInt();</span><br><span class="line">    <span class="type">long</span> <span class="variable">tagsCode</span> <span class="operator">=</span> bufferCQ.getByteBuffer().getLong();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (cq.isExtAddr(tagsCode)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (cq.getExt(tagsCode, cqExtUnit)) &#123;</span><br><span class="line">            tagsCode = cqExtUnit.getTagsCode();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">//can&#x27;t find ext content.So re compute tags code.</span></span><br><span class="line">            log.error(<span class="string">&quot;[BUG] can&#x27;t find consume queue extend file content!addr=&#123;&#125;, offsetPy=&#123;&#125;, sizePy=&#123;&#125;&quot;</span>,</span><br><span class="line">                tagsCode, offsetPy, sizePy);</span><br><span class="line">            <span class="type">long</span> <span class="variable">msgStoreTime</span> <span class="operator">=</span> defaultMessageStore.getCommitLog().pickupStoreTimestamp(offsetPy, sizePy);</span><br><span class="line">            tagsCode = computeDeliverTimestamp(delayLevel, msgStoreTime);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    <span class="type">long</span> <span class="variable">deliverTimestamp</span> <span class="operator">=</span> <span class="built_in">this</span>.correctDeliverTimestamp(now, tagsCode);</span><br><span class="line">    </span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">//根据消息偏移量与消息大小,从CommitLog中查找消息.</span></span><br><span class="line">  	<span class="type">MessageExt</span> <span class="variable">msgExt</span> <span class="operator">=</span></span><br><span class="line">   ScheduleMessageService.<span class="built_in">this</span>.defaultMessageStore.lookMessageByOffset(</span><br><span class="line">       offsetPy, sizePy);</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure>



<h3 id="2-5-8-顺序消息"><a href="#2-5-8-顺序消息" class="headerlink" title="2.5.8 顺序消息"></a>2.5.8 顺序消息</h3><p>顺序消息实现类是org.apache.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService</p>
<p><em><strong>代码：ConsumeMessageOrderlyService#start</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//如果消息模式为集群模式，启动定时任务，默认每隔20s执行一次锁定分配给自己的消息消费队列</span></span><br><span class="line">    <span class="keyword">if</span> (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.<span class="built_in">this</span>.defaultMQPushConsumerImpl.messageModel())) &#123;</span><br><span class="line">        <span class="built_in">this</span>.scheduledExecutorService.scheduleAtFixedRate(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                ConsumeMessageOrderlyService.<span class="built_in">this</span>.lockMQPeriodically();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, <span class="number">1000</span> * <span class="number">1</span>, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：ConsumeMessageOrderlyService#submitConsumeRequest</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//构建消息任务,并提交消费线程池中</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">submitConsumeRequest</span><span class="params">(</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> List&lt;MessageExt&gt; msgs,</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> ProcessQueue processQueue,</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> MessageQueue messageQueue,</span></span><br><span class="line"><span class="params">    <span class="keyword">final</span> <span class="type">boolean</span> dispathToConsume)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (dispathToConsume) &#123;</span><br><span class="line">        <span class="type">ConsumeRequest</span> <span class="variable">consumeRequest</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConsumeRequest</span>(processQueue, messageQueue);</span><br><span class="line">        <span class="built_in">this</span>.consumeExecutor.submit(consumeRequest);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><em><strong>代码：ConsumeMessageOrderlyService$ConsumeRequest#run</strong></em></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//如果消息队列为丢弃,则停止本次消费任务</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">this</span>.processQueue.isDropped()) &#123;</span><br><span class="line">    log.warn(<span class="string">&quot;run, the message queue not be able to consume, because it&#x27;s dropped. &#123;&#125;&quot;</span>, <span class="built_in">this</span>.messageQueue);</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//从消息队列中获取一个对象。然后消费消息时先申请独占objLock锁。顺序消息一个消息消费队列同一时刻只会被一个消费线程池处理</span></span><br><span class="line"><span class="keyword">final</span> <span class="type">Object</span> <span class="variable">objLock</span> <span class="operator">=</span> messageQueueLock.fetchLockObject(<span class="built_in">this</span>.messageQueue);</span><br><span class="line"><span class="keyword">synchronized</span> (objLock) &#123;</span><br><span class="line">	...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="2-5-9-小结"><a href="#2-5-9-小结" class="headerlink" title="2.5.9 小结"></a>2.5.9 小结</h3><p>RocketMQ消息消费方式分别为集群模式、广播模式。</p>
<p>消息队列负载由RebalanceService线程默认每隔20s进行一次消息队列负载，根据当前消费者组内消费者个数与主题队列数量按照某一种负载算法进行队列分配，分配原则为同一个消费者可以分配多个消息消费队列，同一个消息消费队列同一个时间只会分配给一个消费者。</p>
<p>消息拉取由PullMessageService线程根据RebalanceService线程创建的拉取任务进行拉取，默认每次拉取32条消息，提交给消费者消费线程后继续下一次消息拉取。如果消息消费过慢产生消息堆积会触发消息消费拉取流控。</p>
<p>并发消息消费指消费线程池中的线程可以并发对同一个消息队列的消息进行消费，消费成功后，取出消息队列中最小的消息偏移量作为消息消费进度偏移量存储在于消息消费进度存储文件中，集群模式消息消费进度存储在Broker（消息服务器），广播模式消息消费进度存储在消费者端。</p>
<p>RocketMQ不支持任意精度的定时调度消息，只支持自定义的消息延迟级别，例如1s、2s、5s等，可通过在broker配置文件中设置messageDelayLevel。</p>
<p>顺序消息一般使用集群模式，是指对消息消费者内的线程池中的线程对消息消费队列只能串行消费。并并发消息消费最本质的区别是消息消费时必须成功锁定消息消费队列，在Broker端会存储消息消费队列的锁占用情况。</p>

            </div>

            
                <div class="post-copyright-info">
                    
<div class="article-copyright-info-container">
    <ul class="copyright-info-content">
        <li class="post-title">
            <span class="type">本文标题</span>：<span class="content">RocketMQ-03</span>
        </li>
        <li class="post-author">
            <span class="type">本文作者</span>：<span class="content">Blank</span>
        </li>
        <li class="post-time">
            <span class="type">创建时间</span>：<span class="content">2023-02-19 00:00:00</span>
        </li>
        <li class="post-link">
            <span class="type">本文链接</span>：<span class="content">2023/02/19/RocketMQ-03/</span>
        </li>
        <li class="post-license">
            <span class="type">版权声明</span>：<span class="content">本博客所有文章除特别声明外，均采用 <a class="license" target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">BY-NC-SA</a> 许可协议。转载请注明出处！</span>
        </li>
    </ul>
    <div class="copy-copyright-info flex-center tooltip" data-content="复制版权信息" data-offset-y="-2px">
        <i class="fa-solid fa-copy"></i>
    </div>
</div>

                </div>
            

            
                <ul class="post-tags-box">
                    
                        <li class="tag-item">
                            <a href="/tags/%E6%B6%88%E6%81%AF%E4%B8%AD%E9%97%B4%E4%BB%B6/">#消息中间件</a>&nbsp;
                        </li>
                    
                </ul>
            

            
                <div class="article-nav">
                    
                        <div class="article-prev">
                            <a class="prev"
                               rel="prev"
                               href="/2023/02/19/RocketMQ-02/"
                            >
                            <span class="left arrow-icon flex-center">
                              <i class="fas fa-chevron-left"></i>
                            </span>
                                <span class="title flex-center">
                                <span class="post-nav-title-item">RocketMQ-02</span>
                                <span class="post-nav-item">上一篇</span>
                            </span>
                            </a>
                        </div>
                    
                    
                </div>
            

            
        </div>

        
            <div class="toc-content-container">
                <div class="post-toc-wrap">
    <div class="post-toc">
        <ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#RocketMQ-03"><span class="nav-number">1.</span> <span class="nav-text">RocketMQ-03</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#1-%E9%AB%98%E7%BA%A7%E5%8A%9F%E8%83%BD"><span class="nav-number">2.</span> <span class="nav-text">1. 高级功能</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-1-%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8"><span class="nav-number">2.1.</span> <span class="nav-text">1.1 消息存储</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-1-1-%E5%AD%98%E5%82%A8%E4%BB%8B%E8%B4%A8"><span class="nav-number">2.1.1.</span> <span class="nav-text">1.1.1 存储介质</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-1-3-%E6%B6%88%E6%81%AF%E7%9A%84%E5%AD%98%E5%82%A8%E5%92%8C%E5%8F%91%E9%80%81"><span class="nav-number">2.1.2.</span> <span class="nav-text">1.1.3 消息的存储和发送</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8"><span class="nav-number">2.1.2.1.</span> <span class="nav-text">1）消息存储</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-1-4-%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E7%BB%93%E6%9E%84"><span class="nav-number">2.1.3.</span> <span class="nav-text">1.1.4 消息存储结构</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-1-5-%E5%88%B7%E7%9B%98%E6%9C%BA%E5%88%B6"><span class="nav-number">2.1.4.</span> <span class="nav-text">1.1.5 刷盘机制</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E5%90%8C%E6%AD%A5%E5%88%B7%E7%9B%98"><span class="nav-number">2.1.4.1.</span> <span class="nav-text">1）同步刷盘</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2%EF%BC%89%E5%BC%82%E6%AD%A5%E5%88%B7%E7%9B%98"><span class="nav-number">2.1.4.2.</span> <span class="nav-text">2）异步刷盘</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#3%EF%BC%89%E9%85%8D%E7%BD%AE"><span class="nav-number">2.1.4.3.</span> <span class="nav-text">3）配置</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-2-%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7%E6%9C%BA%E5%88%B6"><span class="nav-number">2.2.</span> <span class="nav-text">1.2 高可用性机制</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-1-%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E9%AB%98%E5%8F%AF%E7%94%A8"><span class="nav-number">2.2.1.</span> <span class="nav-text">1.2.1 消息消费高可用</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-2-%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E9%AB%98%E5%8F%AF%E7%94%A8"><span class="nav-number">2.2.2.</span> <span class="nav-text">1.2.2 消息发送高可用</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-2-3-%E6%B6%88%E6%81%AF%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6"><span class="nav-number">2.2.3.</span> <span class="nav-text">1.2.3 消息主从复制</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-3-%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1"><span class="nav-number">2.3.</span> <span class="nav-text">1.3 负载均衡</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-3-1-Producer%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1"><span class="nav-number">2.3.1.</span> <span class="nav-text">1.3.1 Producer负载均衡</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-3-2-Consumer%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1"><span class="nav-number">2.3.2.</span> <span class="nav-text">1.3.2 Consumer负载均衡</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F"><span class="nav-number">2.3.2.1.</span> <span class="nav-text">1）集群模式</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-4-%E6%B6%88%E6%81%AF%E9%87%8D%E8%AF%95"><span class="nav-number">2.4.</span> <span class="nav-text">1.4 消息重试</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-4-1-%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF%E7%9A%84%E9%87%8D%E8%AF%95"><span class="nav-number">2.4.1.</span> <span class="nav-text">1.4.1 顺序消息的重试</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-4-2-%E6%97%A0%E5%BA%8F%E6%B6%88%E6%81%AF%E7%9A%84%E9%87%8D%E8%AF%95"><span class="nav-number">2.4.2.</span> <span class="nav-text">1.4.2 无序消息的重试</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E9%87%8D%E8%AF%95%E6%AC%A1%E6%95%B0"><span class="nav-number">2.4.2.1.</span> <span class="nav-text">1）重试次数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2%EF%BC%89%E9%85%8D%E7%BD%AE%E6%96%B9%E5%BC%8F"><span class="nav-number">2.4.2.2.</span> <span class="nav-text">2）配置方式</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-5-%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97"><span class="nav-number">2.5.</span> <span class="nav-text">1.5 死信队列</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-5-1-%E6%AD%BB%E4%BF%A1%E7%89%B9%E6%80%A7"><span class="nav-number">2.5.1.</span> <span class="nav-text">1.5.1 死信特性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-5-2-%E6%9F%A5%E7%9C%8B%E6%AD%BB%E4%BF%A1%E4%BF%A1%E6%81%AF"><span class="nav-number">2.5.2.</span> <span class="nav-text">1.5.2 查看死信信息</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#1-6-%E6%B6%88%E8%B4%B9%E5%B9%82%E7%AD%89"><span class="nav-number">2.6.</span> <span class="nav-text">1.6 消费幂等</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-6-1-%E6%B6%88%E8%B4%B9%E5%B9%82%E7%AD%89%E7%9A%84%E5%BF%85%E8%A6%81%E6%80%A7"><span class="nav-number">2.6.1.</span> <span class="nav-text">1.6.1 消费幂等的必要性</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#1-6-2-%E5%A4%84%E7%90%86%E6%96%B9%E5%BC%8F"><span class="nav-number">2.6.2.</span> <span class="nav-text">1.6.2 处理方式</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#2-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="nav-number">3.</span> <span class="nav-text">2. 源码分析</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#2-1-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA"><span class="nav-number">3.1.</span> <span class="nav-text">2.1 环境搭建</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-1-%E6%BA%90%E7%A0%81%E6%8B%89%E5%8F%96"><span class="nav-number">3.1.1.</span> <span class="nav-text">2.1.1 源码拉取</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-1-3-%E8%B0%83%E8%AF%95"><span class="nav-number">3.1.2.</span> <span class="nav-text">2.1.3 调试</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E5%90%AF%E5%8A%A8NameServer"><span class="nav-number">3.1.2.1.</span> <span class="nav-text">1）启动NameServer</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2%EF%BC%89%E5%90%AF%E5%8A%A8Broker"><span class="nav-number">3.1.2.2.</span> <span class="nav-text">2）启动Broker</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#3%EF%BC%89%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF"><span class="nav-number">3.1.2.3.</span> <span class="nav-text">3）发送消息</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#4%EF%BC%89%E6%B6%88%E8%B4%B9%E6%B6%88%E6%81%AF"><span class="nav-number">3.1.2.4.</span> <span class="nav-text">4）消费消息</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-2-NameServer"><span class="nav-number">3.2.</span> <span class="nav-text">2.2 NameServer</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-1-%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1"><span class="nav-number">3.2.1.</span> <span class="nav-text">2.2.1 架构设计</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-2-%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B"><span class="nav-number">3.2.2.</span> <span class="nav-text">2.2.2 启动流程</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%AD%A5%E9%AA%A4%E4%BA%8C"><span class="nav-number">3.2.2.1.</span> <span class="nav-text">步骤二</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%AD%A5%E9%AA%A4%E4%B8%89"><span class="nav-number">3.2.2.2.</span> <span class="nav-text">步骤三</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-3-%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86"><span class="nav-number">3.2.3.</span> <span class="nav-text">2.2.3 路由管理</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-3-1-%E8%B7%AF%E7%94%B1%E5%85%83%E4%BF%A1%E6%81%AF"><span class="nav-number">3.2.3.1.</span> <span class="nav-text">2.2.3.1 路由元信息</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-3-2-%E8%B7%AF%E7%94%B1%E6%B3%A8%E5%86%8C"><span class="nav-number">3.2.3.2.</span> <span class="nav-text">2.2.3.2 路由注册</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E5%8F%91%E9%80%81%E5%BF%83%E8%B7%B3%E5%8C%85"><span class="nav-number">3.2.3.3.</span> <span class="nav-text">1）发送心跳包</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#2%EF%BC%89%E5%A4%84%E7%90%86%E5%BF%83%E8%B7%B3%E5%8C%85"><span class="nav-number">3.2.3.3.1.</span> <span class="nav-text">2）处理心跳包</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-3-3-%E8%B7%AF%E7%94%B1%E5%88%A0%E9%99%A4"><span class="nav-number">3.2.3.4.</span> <span class="nav-text">2.2.3.3 路由删除</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2-2-3-4-%E8%B7%AF%E7%94%B1%E5%8F%91%E7%8E%B0"><span class="nav-number">3.2.3.5.</span> <span class="nav-text">2.2.3.4 路由发现</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-2-4-%E5%B0%8F%E7%BB%93"><span class="nav-number">3.2.4.</span> <span class="nav-text">2.2.4 小结</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-3-Producer"><span class="nav-number">3.3.</span> <span class="nav-text">2.3 Producer</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-2-%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B"><span class="nav-number">3.3.1.</span> <span class="nav-text">2.3.2 启动流程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-3-%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81"><span class="nav-number">3.3.2.</span> <span class="nav-text">2.3.3 消息发送</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#3%EF%BC%89%E9%80%89%E6%8B%A9%E9%98%9F%E5%88%97"><span class="nav-number">3.3.2.1.</span> <span class="nav-text">3）选择队列</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-3-4-%E6%89%B9%E9%87%8F%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81"><span class="nav-number">3.3.3.</span> <span class="nav-text">2.3.4 批量消息发送</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-4-%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8"><span class="nav-number">3.4.</span> <span class="nav-text">2.4 消息存储</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-2-%E6%B6%88%E6%81%AF%E5%AD%98%E5%82%A8%E6%B5%81%E7%A8%8B"><span class="nav-number">3.4.1.</span> <span class="nav-text">2.4.2 消息存储流程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-3-%E5%AD%98%E5%82%A8%E6%96%87%E4%BB%B6"><span class="nav-number">3.4.2.</span> <span class="nav-text">2.4.3 存储文件</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-4-%E5%AD%98%E5%82%A8%E6%96%87%E4%BB%B6%E5%86%85%E5%AD%98%E6%98%A0%E5%B0%84"><span class="nav-number">3.4.3.</span> <span class="nav-text">2.4.4 存储文件内存映射</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#3%EF%BC%89TransientStorePool"><span class="nav-number">3.4.3.1.</span> <span class="nav-text">3）TransientStorePool</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-5-%E5%AE%9E%E6%97%B6%E6%9B%B4%E6%96%B0%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E9%98%9F%E5%88%97%E4%B8%8E%E7%B4%A2%E5%BC%95%E6%96%87%E4%BB%B6"><span class="nav-number">3.4.4.</span> <span class="nav-text">2.4.5 实时更新消息消费队列与索引文件</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89%E8%BD%AC%E5%8F%91%E5%88%B0ConsumerQueue"><span class="nav-number">3.4.4.1.</span> <span class="nav-text">1）转发到ConsumerQueue</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#2%EF%BC%89%E8%BD%AC%E5%8F%91%E5%88%B0Index"><span class="nav-number">3.4.4.2.</span> <span class="nav-text">2）转发到Index</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-6-%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E5%92%8C%E7%B4%A2%E5%BC%95%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D"><span class="nav-number">3.4.5.</span> <span class="nav-text">2.4.6 消息队列和索引文件恢复</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-7-%E5%88%B7%E7%9B%98%E6%9C%BA%E5%88%B6"><span class="nav-number">3.4.6.</span> <span class="nav-text">2.4.7 刷盘机制</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%90%8C%E6%AD%A5%E5%88%B7%E7%9B%98"><span class="nav-number">3.4.6.1.</span> <span class="nav-text">同步刷盘</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%BC%82%E6%AD%A5%E5%88%B7%E7%9B%98"><span class="nav-number">3.4.6.2.</span> <span class="nav-text">异步刷盘</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-8-%E8%BF%87%E6%9C%9F%E6%96%87%E4%BB%B6%E5%88%A0%E9%99%A4%E6%9C%BA%E5%88%B6"><span class="nav-number">3.4.7.</span> <span class="nav-text">2.4.8 过期文件删除机制</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-4-9-%E5%B0%8F%E7%BB%93"><span class="nav-number">3.4.8.</span> <span class="nav-text">2.4.9 小结</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-5-Consumer"><span class="nav-number">3.5.</span> <span class="nav-text">2.5 Consumer</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-1-%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E6%A6%82%E8%BF%B0"><span class="nav-number">3.5.1.</span> <span class="nav-text">2.5.1 消息消费概述</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-3-%E6%B6%88%E8%B4%B9%E8%80%85%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B"><span class="nav-number">3.5.2.</span> <span class="nav-text">2.5.3 消费者启动流程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-4-%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96"><span class="nav-number">3.5.3.</span> <span class="nav-text">2.5.4 消息拉取</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#1%EF%BC%89PullMessageService%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6"><span class="nav-number">3.5.3.1.</span> <span class="nav-text">1）PullMessageService实现机制</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#3%EF%BC%89%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B"><span class="nav-number">3.5.3.2.</span> <span class="nav-text">3）消息拉取基本流程</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#4-%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96%E6%80%BB%E7%BB%93"><span class="nav-number">3.5.3.2.1.</span> <span class="nav-text">4.消息拉取总结</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#4%EF%BC%89%E6%B6%88%E6%81%AF%E6%8B%89%E5%8F%96%E9%95%BF%E8%BD%AE%E8%AF%A2%E6%9C%BA%E5%88%B6%E5%88%86%E6%9E%90"><span class="nav-number">3.5.3.3.</span> <span class="nav-text">4）消息拉取长轮询机制分析</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-5-%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97%E8%B4%9F%E8%BD%BD%E4%B8%8E%E9%87%8D%E6%96%B0%E5%88%86%E5%B8%83%E6%9C%BA%E5%88%B6"><span class="nav-number">3.5.4.</span> <span class="nav-text">2.5.5 消息队列负载与重新分布机制</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-6-%E6%B6%88%E6%81%AF%E6%B6%88%E8%B4%B9%E8%BF%87%E7%A8%8B"><span class="nav-number">3.5.5.</span> <span class="nav-text">2.5.6 消息消费过程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-7-%E5%AE%9A%E6%97%B6%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6"><span class="nav-number">3.5.6.</span> <span class="nav-text">2.5.7 定时消息机制</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-8-%E9%A1%BA%E5%BA%8F%E6%B6%88%E6%81%AF"><span class="nav-number">3.5.7.</span> <span class="nav-text">2.5.8 顺序消息</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-5-9-%E5%B0%8F%E7%BB%93"><span class="nav-number">3.5.8.</span> <span class="nav-text">2.5.9 小结</span></a></li></ol></li></ol></li></ol>
    </div>
</div>

            </div>
        
    </div>
</div>


                
            </div>

        </div>

        <div class="page-main-content-bottom">
            
<footer class="footer">
    <div class="info-container">
        <div class="copyright-info info-item">
            &copy;
            
            2024
            
                &nbsp;<i class="fas fa-heart icon-animate"></i>
                &nbsp;<a href="/">Blank</a>
            
        </div>
        
            <script async data-pjax
                    src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
            <div class="website-count info-item">
                
                
            </div>
        
        <div class="theme-info info-item">
            由 <a target="_blank" href="https://hexo.io">Hexo</a> 驱动&nbsp;|&nbsp;主题&nbsp;<a class="theme-version" target="_blank" href="https://github.com/XPoet/hexo-theme-keep">Keep v3.6.1</a>
        </div>
        
        
            <div class="deploy-info info-item">
                
                    <a target="_blank" rel="nofollow" href="https://gitee.com/lucky0915">
                
                    本站由 <span class="tooltip" data-content="Gitee Pages"><img src="/images/deploy-provider/gitee.png"></span> 提供部署服务
                
                    </a>
                
            </div>
        
    </div>
</footer>

        </div>
    </div>

    
        <div class="post-tools">
            <div class="post-tools-container">
    <ul class="tools-list">
        <!-- TOC aside toggle -->
        
            <li class="tools-item flex-center toggle-show-toc">
                <i class="fas fa-list"></i>
            </li>
        

        <!-- go comment -->
        
    </ul>
</div>

        </div>
    

    <div class="right-bottom-side-tools">
        <div class="side-tools-container">
    <ul class="side-tools-list">
        <li class="tools-item tool-font-adjust-plus flex-center">
            <i class="fas fa-search-plus"></i>
        </li>

        <li class="tools-item tool-font-adjust-minus flex-center">
            <i class="fas fa-search-minus"></i>
        </li>

        <li class="tools-item tool-dark-light-toggle flex-center">
            <i class="fas fa-moon"></i>
        </li>

        <!-- rss -->
        

        

        <li class="tools-item tool-scroll-to-bottom flex-center">
            <i class="fas fa-arrow-down"></i>
        </li>
    </ul>

    <ul class="exposed-tools-list">
        <li class="tools-item tool-toggle-show flex-center">
            <i class="fas fa-cog fa-spin"></i>
        </li>
        
            <li class="tools-item tool-scroll-to-top flex-center">
                <i class="arrow-up fas fa-arrow-up"></i>
                <span class="percent"></span>
            </li>
        
    </ul>
</div>

    </div>

    <div class="zoom-in-image-mask">
    <img class="zoom-in-image">
</div>


    
        <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
          <span class="search-input-field-pre">
            <i class="fas fa-keyboard"></i>
          </span>
            <div class="search-input-container">
                <input autocomplete="off"
                       autocorrect="off"
                       autocapitalize="off"
                       placeholder="搜索..."
                       spellcheck="false"
                       type="search"
                       class="search-input"
                >
            </div>
            <span class="close-popup-btn">
                <i class="fas fa-times"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fas fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>

    

</main>



<script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/utils.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/main.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/header-shrink.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/back2top.js"></script><script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/dark-light-toggle.js"></script>




    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/local-search.js"></script>



    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/code-block.js"></script>



    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/lazyload.js"></script>


<div class="post-scripts pjax">
    
        <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/post-helper.js"></script>
        
            <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/libs/anime.min.js"></script>
        
        
            <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/toc.js"></script>
        
    
</div>


    <script src="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/js/libs/pjax.min.js"></script>
<script>
    window.addEventListener('DOMContentLoaded', () => {
        window.pjax = new Pjax({
            selectors: [
                'head title',
                '.page-container',
                '.pjax'
            ],
            history: true,
            debug: false,
            cacheBust: false,
            timeout: 0,
            analytics: false,
            currentUrlFullReload: false,
            scrollRestoration: false,
            // scrollTo: true,
        });

        document.addEventListener('pjax:send', () => {
            KEEP.utils.pjaxProgressBarStart();
        });

        document.addEventListener('pjax:complete', () => {
            KEEP.utils.pjaxProgressBarEnd();
            window.pjax.executeScripts(document.querySelectorAll('script[data-pjax], .pjax script'));
            KEEP.refresh();
        });
    });
</script>



</body>
</html>
